Merge "Fix ANR in creating the fallback animation." into gb-ub-photos-arches
diff --git a/Android.mk b/Android.mk
index e2914bb..209c7d5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,7 +22,7 @@
 
 LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D
 
-LOCAL_SDK_VERSION := 16
+LOCAL_SDK_VERSION := current
 
 # If this is an unbundled build (to install seprately) then include
 # the libraries in the APK, otherwise just put them in /system/lib and
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 738ae3d..a393025 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -164,6 +164,9 @@
     public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK =
             Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
 
+    public static final boolean HAS_VIEW_PROPERTY_ANIMATOR =
+            Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
+
     public static int getIntFieldIfExists(Class<?> klass, String fieldName,
             Class<?> obj, int defaultVal) {
         try {
diff --git a/res/drawable/btn_undo.xml b/res/drawable/btn_undo.xml
new file mode 100644
index 0000000..3323255
--- /dev/null
+++ b/res/drawable/btn_undo.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_enabled="false" android:drawable="@drawable/filtershow_button_undo_disabled" />
+    <item android:drawable="@drawable/filtershow_button_undo" />
+</selector>
+
diff --git a/res/drawable/filtershow_button_undo_disabled.png b/res/drawable/filtershow_button_undo_disabled.png
new file mode 100644
index 0000000..6d42aa8
--- /dev/null
+++ b/res/drawable/filtershow_button_undo_disabled.png
Binary files differ
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
index 1449f16..3cf0c82 100644
--- a/res/layout/filtershow_activity.xml
+++ b/res/layout/filtershow_activity.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!--
+     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.
@@ -22,9 +23,9 @@
         android:id="@+id/imageStatePanel"
         android:layout_width="200dip"
         android:layout_height="match_parent"
+        android:layout_gravity="right"
         android:orientation="vertical"
-        android:visibility="invisible"
-        android:layout_gravity="right">
+        android:visibility="invisible" >
 
         <TextView
             android:layout_width="match_parent"
@@ -49,9 +50,9 @@
         android:id="@+id/historyPanel"
         android:layout_width="200dip"
         android:layout_height="match_parent"
+        android:layout_gravity="right"
         android:orientation="vertical"
-        android:visibility="invisible"
-        android:layout_gravity="right">
+        android:visibility="invisible" >
 
         <TextView
             android:layout_width="match_parent"
@@ -77,16 +78,16 @@
             android:orientation="horizontal" >
 
             <Button
-                android:gravity="center"
                 android:id="@+id/resetOperationsButton"
                 style="@style/FilterShowHistoryButton"
+                android:gravity="center"
                 android:text="@string/reset" />
 
             <Button
                 android:id="@+id/saveOperationsButton"
                 style="@style/FilterShowHistoryButton"
-                android:visibility="gone"
-                android:text="@string/save" />
+                android:text="@string/save"
+                android:visibility="gone" />
         </LinearLayout>
     </LinearLayout>
 
@@ -131,6 +132,7 @@
                 android:layout_height="wrap_content"
                 android:visibility="gone" />
 
+            <!--
             <ImageButton
                 android:id="@+id/showOriginalButton"
                 android:layout_width="64dip"
@@ -138,143 +140,192 @@
                 android:layout_gravity="bottom"
                 android:scaleType="centerInside"
                 android:src="@drawable/filtershow_button_show_original" />
+                 -->
+            <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" />
         </FrameLayout>
 
-        <HorizontalScrollView
-            android:id="@+id/fxList"
-            android:layout_width="match_parent"
-            android:layout_height="96dip" >
-
-            <LinearLayout
-                android:id="@+id/listFilters"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:orientation="horizontal" >
-            </LinearLayout>
-        </HorizontalScrollView>
-
-        <HorizontalScrollView
-            android:id="@+id/bordersList"
-            android:layout_width="match_parent"
-            android:layout_height="96dip"
-            android:visibility="gone" >
-
-            <LinearLayout
-                android:id="@+id/listBorders"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:orientation="horizontal" >
-            </LinearLayout>
-        </HorizontalScrollView>
-
-        <HorizontalScrollView
-            android:id="@+id/gemoetryList"
+        <FrameLayout
+            android:id="@+id/secondRowPanel"
             android:layout_width="fill_parent"
-            android:layout_height="96dip"
-            android:background="@color/background_toolbar"
-            android:visibility="gone" >
+            android:layout_height="wrap_content" >
 
             <LinearLayout
-                android:id="@+id/listGeometry"
-                android:layout_width="wrap_content"
-                android:layout_height="fill_parent"
-                android:orientation="horizontal" >
+                android:id="@+id/filterButtonsList"
+                android:layout_width="fill_parent"
+                android:layout_height="96dip"
+                android:background="@color/background_toolbar"
+                android:orientation="horizontal"
+                android:visibility="gone" >
 
                 <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/straightenButton"
+                    android:id="@+id/compareWithOriginalImage"
                     style="@style/FilterShowBottomButton"
+                    android:layout_gravity="left"
+                    android:layout_weight="0"
                     android:src="@drawable/filtershow_button_geometry_straighten"
-                    android:text="@string/straighten" />
+                    android:text="@string/compare_original" />
+
+                <Button
+                    android:id="@+id/applyEffect"
+                    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="24dip" />
 
                 <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/cropButton"
+                    android:id="@+id/resetEffect"
                     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/flip" />
+                    android:layout_gravity="right"
+                    android:layout_weight="0"
+                    android:src="@drawable/filtershow_button_geometry_straighten"
+                    android:text="@string/reset_effect" />
             </LinearLayout>
-        </HorizontalScrollView>
 
-        <HorizontalScrollView
-            android:id="@+id/colorsFxList"
-            android:layout_width="fill_parent"
-            android:layout_height="96dip"
-            android:background="@color/background_toolbar"
-            android:visibility="gone" >
+            <HorizontalScrollView
+                android:id="@+id/fxList"
+                android:layout_width="match_parent"
+                android:layout_height="96dip">
 
-            <LinearLayout
-                android:id="@+id/listColorsFx"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal" >
+                <LinearLayout
+                    android:id="@+id/listFilters"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:orientation="horizontal" >
+                </LinearLayout>
+            </HorizontalScrollView>
 
-                <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/vignetteButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_vignette"
-                    android:text="@string/vignette" />
+            <HorizontalScrollView
+                android:id="@+id/bordersList"
+                android:layout_width="match_parent"
+                android:layout_height="96dip"
+                android:visibility="gone" >
 
-                <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/exposureButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_contrast"
-                    android:text="@string/exposure" />
+                <LinearLayout
+                    android:id="@+id/listBorders"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:orientation="horizontal" >
+                </LinearLayout>
+            </HorizontalScrollView>
 
-                <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/contrastButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_contrast"
-                    android:text="@string/contrast" />
+            <HorizontalScrollView
+                android:id="@+id/geometryList"
+                android:layout_width="fill_parent"
+                android:layout_height="96dip"
+                android:background="@color/background_toolbar"
+                android:visibility="gone" >
 
-                <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/saturationButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_contrast"
-                    android:text="@string/saturation" />
+                <LinearLayout
+                    android:id="@+id/listGeometry"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:orientation="horizontal" >
 
-                <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                    android:id="@+id/tintButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_contrast"
-                    android:text="@string/tint" />
+                    <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/curvesButtonRGB"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_curve"
-                    android:text="@string/curvesRGB" />
+                    <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/vibranceButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_contrast"
-                    android:text="@string/vibrance" />
+                    <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/sharpenButton"
-                    style="@style/FilterShowBottomButton"
-                    android:src="@drawable/filtershow_button_colors_sharpen"
-                    android:text="@string/sharpen" />
+                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+                        android:id="@+id/flipButton"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_geometry_flip"
+                        android:text="@string/flip" />
+                </LinearLayout>
+            </HorizontalScrollView>
 
-                <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" />
+            <HorizontalScrollView
+                android:id="@+id/colorsFxList"
+                android:layout_width="fill_parent"
+                android:layout_height="96dip"
+                android:background="@color/background_toolbar"
+                android:visibility="gone" >
 
-            </LinearLayout>
-        </HorizontalScrollView>
+                <LinearLayout
+                    android:id="@+id/listColorsFx"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal" >
+
+                    <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/vibranceButton"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_colors_contrast"
+                        android:text="@string/vibrance" />
+
+                    <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/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/vignetteButton"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_colors_vignette"
+                        android:text="@string/vignette" />
+
+                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+                        android:id="@+id/sharpenButton"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_colors_sharpen"
+                        android:text="@string/sharpen" />
+
+                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+                        android:id="@+id/tintButton"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_colors_contrast"
+                        android:text="@string/tint" />
+
+                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+                        android:id="@+id/curvesButtonRGB"
+                        style="@style/FilterShowBottomButton"
+                        android:src="@drawable/filtershow_button_colors_curve"
+                        android:text="@string/curvesRGB" />
+
+                </LinearLayout>
+            </HorizontalScrollView>
+        </FrameLayout>
 
         <LinearLayout
             android:layout_width="match_parent"
@@ -288,7 +339,7 @@
                 android:layout_weight="1"
                 android:background="@drawable/filtershow_button_background"
                 android:scaleType="centerInside"
-                android:src="@drawable/filtershow_button_fx" />
+                android:src="@drawable/ic_photoeditor_effects" />
 
             <ImageButton
                 android:id="@+id/borderButton"
@@ -298,7 +349,7 @@
                 android:background="@drawable/filtershow_button_background"
                 android:padding="2dip"
                 android:scaleType="centerInside"
-                android:src="@drawable/filtershow_button_border" />
+                android:src="@drawable/ic_photoeditor_border" />
 
             <ImageButton
                 android:id="@+id/geometryButton"
@@ -308,7 +359,7 @@
                 android:background="@drawable/filtershow_button_background"
                 android:padding="2dip"
                 android:scaleType="centerInside"
-                android:src="@drawable/filtershow_button_geometry" />
+                android:src="@drawable/ic_photoeditor_fix" />
 
             <ImageButton
                 android:id="@+id/colorsButton"
@@ -318,7 +369,7 @@
                 android:background="@drawable/filtershow_button_background"
                 android:padding="2dip"
                 android:scaleType="centerInside"
-                android:src="@drawable/filtershow_button_colors" />
+                android:src="@drawable/ic_photoeditor_color" />
         </LinearLayout>
     </LinearLayout>
 
diff --git a/res/layout/photopage_bottom_controls.xml b/res/layout/photopage_bottom_controls.xml
index 5b80cf2..434c598 100644
--- a/res/layout/photopage_bottom_controls.xml
+++ b/res/layout/photopage_bottom_controls.xml
@@ -15,21 +15,18 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentLeft="true"
+                android:layout_alignParentBottom="true"
                 android:paddingTop="5dp"
                 android:paddingBottom="5dp"
                 android:paddingLeft="15dp"
                 android:paddingRight="15dp"
                 android:visibility="gone"/>
-        <ImageButton
+        <ImageView
                 android:id="@+id/photopage_bottom_control_panorama"
                 android:src="@drawable/ic_menu_photosphere"
-                android:background="@drawable/photopage_bottom_button_background"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentRight="true"
-                android:paddingTop="5dp"
-                android:paddingBottom="5dp"
-                android:paddingLeft="15dp"
-                android:paddingRight="15dp"
+                android:layout_width="70dp"
+                android:layout_height="70dp"
+                android:layout_centerHorizontal="true"
+                android:layout_alignParentBottom="true"
                 android:visibility="gone"/>
 </RelativeLayout>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 3dd08d3..9b84386 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -181,7 +181,7 @@
     <string name="no_external_storage" msgid="95726173164068417">"Žádné externí úložiště není k dispozici"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Filmový pás"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Mřížka"</string>
-    <string name="trimming" msgid="9122385768369143997">"Zkrácení"</string>
+    <string name="trimming" msgid="9122385768369143997">"Zkracování"</string>
     <string name="please_wait" msgid="7296066089146487366">"Čekejte prosím"</string>
     <string name="save_into" msgid="6688364520925473396">"Uložit zkrácené video do"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Zkrácení nelze provést: výsledné video je příliš krátké"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f90cfd1..8c13731 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Tiada storan luar tersedia"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Jalur filem"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Grid"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Mencantas"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Sila tunggu"</string>
+    <string name="save_into" msgid="6688364520925473396">"Simpan video yang dicantas ke"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Tidak boleh mencantas: video sasaran terlalu pendek"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Tidak dicantas: panjang yang sama seperti yang asli"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 9272210..4d83aa1 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Ekstern lagringsplass ikke tilgjengelig"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Filmstripe"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Rutenett"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Beskjærer"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Et øyeblikk"</string>
+    <string name="save_into" msgid="6688364520925473396">"Lagre den beskjærte videoen i"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Videoen kan ikke beskjæres – målvideoen er for kort"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Videoen ble ikke beskjært – lengden er den samme som originalen"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index bad5ec6..3ce3461 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nu este disponibilă nicio stocare externă"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Bandă de film"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Grilă"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Se ajustează"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Aşteptaţi"</string>
+    <string name="save_into" msgid="6688364520925473396">"Salvaţi videoclipul ajustat în"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Nu se poate ajusta: videoclipul ţintă este prea scurt"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Nu aţi efectuat ajustări: videoclipul are aceeaşi durată ca şi cel iniţial"</string>
 </resources>
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
index 7d86138..9fd5b92 100644
--- a/res/values/filtershow_strings.xml
+++ b/res/values/filtershow_strings.xml
@@ -52,6 +52,15 @@
     <!--  Text for the image state panel title [CHAR LIMIT=50] -->
     <string name="imageState">Current Image State</string>
 
+    <!--  Additional filters buttons  -->
+
+    <!--  Label for the compare original image filter button [CHAR LIMIT=15] -->
+    <string name="compare_original">Compare</string>
+    <!--  Label for the apply effect button [CHAR LIMIT=15] -->
+    <string name="apply_effect">Apply</string>
+    <!--  Label for the reset effect button [CHAR LIMIT=15] -->
+    <string name="reset_effect">Reset</string>
+
     <!--  Filters buttons -->
 
     <!--  Label for the exposure filter button [CHAR LIMIT=15] -->
diff --git a/res/values/filtershow_styles.xml b/res/values/filtershow_styles.xml
index 14d0d94..4600eeb 100644
--- a/res/values/filtershow_styles.xml
+++ b/res/values/filtershow_styles.xml
@@ -41,7 +41,7 @@
     </style>
 
     <style name="FilterShowBottomButton">
-        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_width">96dip</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:background">@drawable/filtershow_button_background</item>
         <item name="android:gravity">center</item>
diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java
index dadb32c..6ca4bab 100644
--- a/src/com/android/gallery3d/app/Gallery.java
+++ b/src/com/android/gallery3d/app/Gallery.java
@@ -212,26 +212,6 @@
                     }
                 }
 
-                // Displays the filename as title, reading the filename from the interface:
-                // {@link android.provider.OpenableColumns#DISPLAY_NAME}.
-                AsyncQueryHandler queryHandler = new AsyncQueryHandler(getContentResolver()) {
-                    @Override
-                    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                        try {
-                            if ((cursor != null) && cursor.moveToFirst()) {
-                                String displayName = cursor.getString(0);
-
-                                // Just show empty title if other apps don't set DISPLAY_NAME
-                                setTitle((displayName == null) ? "" : displayName);
-                            }
-                        } finally {
-                            Utils.closeSilently(cursor);
-                        }
-                    }
-                };
-                queryHandler.startQuery(0, null, uri, new String[] {OpenableColumns.DISPLAY_NAME},
-                        null, null, null);
-
                 getStateManager().startState(PhotoPage.class, data);
             }
         }
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index 5ab022a..66f2874 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -35,7 +35,6 @@
 import com.android.gallery3d.ui.ScreenNail;
 import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.ui.TileImageViewAdapter;
-import com.android.gallery3d.ui.TiledTexture;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 import com.android.gallery3d.util.MediaSetUtils;
@@ -60,8 +59,8 @@
     private static final int MSG_RUN_OBJECT = 3;
     private static final int MSG_UPDATE_IMAGE_REQUESTS = 4;
 
-    private static final int MIN_LOAD_COUNT = 16;
-    private static final int DATA_CACHE_SIZE = 256;
+    private static final int MIN_LOAD_COUNT = 8;
+    private static final int DATA_CACHE_SIZE = 32;
     private static final int SCREEN_NAIL_MAX = PhotoView.SCREEN_NAIL_MAX;
     private static final int IMAGE_CACHE_SIZE = 2 * SCREEN_NAIL_MAX + 1;
 
@@ -163,7 +162,6 @@
     private DataListener mDataListener;
 
     private final SourceListener mSourceListener = new SourceListener();
-    private final TiledTexture.Uploader mUploader;
 
     // The path of the current viewing item will be stored in mItemPath.
     // If mItemPath is not null, mCurrentIndex is only a hint for where we
@@ -185,8 +183,6 @@
 
         Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
 
-        mUploader = new TiledTexture.Uploader(activity.getGLRoot());
-
         mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
             @SuppressWarnings("unchecked")
             @Override
@@ -325,7 +321,6 @@
             }
         }
         updateImageRequests();
-        updateScreenNailUploadQueue();
     }
 
     private void updateFullImage(Path path, Future<BitmapRegionDecoder> future) {
@@ -350,8 +345,6 @@
     @Override
     public void resume() {
         mIsActive = true;
-        TiledTexture.prepareResources();
-
         mSource.addContentListener(mSourceListener);
         updateImageCache();
         updateImageRequests();
@@ -378,9 +371,6 @@
         }
         mImageCache.clear();
         mTileProvider.clear();
-
-        mUploader.clear();
-        TiledTexture.freeResources();
     }
 
     private MediaItem getItem(int index) {
@@ -404,7 +394,6 @@
         updateImageCache();
         updateImageRequests();
         updateTileProvider();
-        updateScreenNailUploadQueue();
 
         if (mDataListener != null) {
             mDataListener.onPhotoChanged(index, mItemPath);
@@ -413,32 +402,6 @@
         fireDataChange();
     }
 
-    private void uploadScreenNail(int offset) {
-        int index = mCurrentIndex + offset;
-        if (index < mActiveStart || index >= mActiveEnd) return;
-
-        MediaItem item = getItem(index);
-        if (item == null) return;
-
-        ImageEntry e = mImageCache.get(item.getPath());
-        if (e == null) return;
-
-        ScreenNail s = e.screenNail;
-        if (s instanceof BitmapScreenNail) {
-            TiledTexture t = ((BitmapScreenNail) s).getTexture();
-            if (t != null && !t.isReady()) mUploader.addTexture(t);
-        }
-    }
-
-    private void updateScreenNailUploadQueue() {
-        mUploader.clear();
-        uploadScreenNail(0);
-        for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
-            uploadScreenNail(i);
-            uploadScreenNail(-i);
-        }
-    }
-
     @Override
     public void moveTo(int index) {
         updateCurrentIndex(index);
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 2a088b1..7f6d773 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -149,6 +149,7 @@
     private MediaItem mCurrentPhoto = null;
     private MenuExecutor mMenuExecutor;
     private boolean mIsActive;
+    private boolean mShowSpinner;
     private String mSetPathString;
     // This is the original mSetPathString before adding the camera preview item.
     private String mOriginalSetPathString;
@@ -161,6 +162,7 @@
     private boolean mStartInFilmstrip;
     private boolean mInCameraRoll;
     private boolean mStartedFromAlbumPage;
+    private boolean mRecenterCameraOnResume = true;
 
     private long mCameraSwitchCutoff = 0;
     private boolean mSkipUpdateCurrentPhoto = false;
@@ -294,19 +296,19 @@
                     }
                     case MSG_ON_CAMERA_CENTER: {
                         mSkipUpdateCurrentPhoto = false;
-                        boolean updateNeeded = false;
+                        boolean stayedOnCamera = false;
                         if (!mPhotoView.getFilmMode()) {
-                            lockOrientation();
-                            updateNeeded = true;
+                            stayedOnCamera = true;
                         } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff &&
                                 mMediaSet.getMediaItemCount() > 1) {
                             mPhotoView.switchToImage(1);
                         } else {
                             mPhotoView.setFilmMode(false);
-                            updateNeeded = true;
+                            stayedOnCamera = true;
                         }
 
-                        if (updateNeeded) {
+                        if (stayedOnCamera) {
+                            lockOrientation();
                             updateBars();
                             updateCurrentPhoto(mModel.getMediaItem(0));
                         }
@@ -339,6 +341,7 @@
                         MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED;
         mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
         if (mSetPathString != null) {
+            mShowSpinner = true;
             mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
             if (mAppBridge != null) {
                 mShowBars = false;
@@ -360,6 +363,7 @@
                 if (SecureSource.isSecurePath(mSetPathString)) {
                     mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
                             .getMediaSet(mSetPathString);
+                    mShowSpinner = false;
                 }
                 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
                     // Set the flag to be on top of the lock screen.
@@ -441,7 +445,7 @@
                             mCameraSwitchCutoff = SystemClock.uptimeMillis() +
                                     CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
                             mPhotoView.stopScrolling();
-                        } else if (oldIndex == 1 && mCurrentIndex == 0) {
+                        } else if (oldIndex >= 1 && mCurrentIndex == 0) {
                             mPhotoView.setWantPictureCenterCallbacks(true);
                             mSkipUpdateCurrentPhoto = true;
                         }
@@ -483,6 +487,7 @@
             mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
             mPhotoView.setModel(mModel);
             updateCurrentPhoto(mediaItem);
+            mShowSpinner = false;
         }
 
         mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
@@ -503,14 +508,18 @@
     }
 
     public boolean canDisplayBottomControls() {
-        return mShowBars && !mPhotoView.getFilmMode();
+        return mIsActive && !mPhotoView.getFilmMode();
     }
 
     public boolean canDisplayBottomControl(int control) {
         if (mCurrentPhoto == null) return false;
         switch(control) {
             case R.id.photopage_bottom_control_edit:
-                return mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
+                return mHaveImageEditor && mShowBars
+                        && (mCurrentPhoto.getSupportedOperations()
+                        & MediaItem.SUPPORT_EDIT) != 0
+                        && mCurrentPhoto.getMediaType()
+                        == MediaObject.MEDIA_TYPE_IMAGE;
             case R.id.photopage_bottom_control_panorama:
                 return (mCurrentPhoto.getSupportedOperations()
                         & MediaItem.SUPPORT_PANORAMA) != 0;
@@ -525,6 +534,7 @@
                 launchPhotoEditor();
                 return;
             case R.id.photopage_bottom_control_panorama:
+                mRecenterCameraOnResume = false;
                 LightCycleHelper.viewPanorama(mActivity, mCurrentPhoto.getContentUri());
                 return;
             default:
@@ -590,6 +600,7 @@
                 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
             intent.setAction(Intent.ACTION_EDIT);
         }
+        mRecenterCameraOnResume = false;
         ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
                 REQUEST_EDIT);
     }
@@ -1074,7 +1085,8 @@
         mDeletePath = null;
     }
 
-    public static void playVideo(Activity activity, Uri uri, String title) {
+    public void playVideo(Activity activity, Uri uri, String title) {
+        mRecenterCameraOnResume = false;
         try {
             Intent intent = new Intent(Intent.ACTION_VIEW)
                     .setDataAndType(uri, "video/*")
@@ -1185,8 +1197,12 @@
         }
         mPhotoView.pause();
         mHandler.removeMessages(MSG_HIDE_BARS);
+        mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
+        if (mBottomControls != null) {
+            mBottomControls.refresh();
+        }
         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
-        if (mSecureAlbum == null) {
+        if (mShowSpinner) {
             mActionBar.disableAlbumModeMenu(true);
         }
         onCommitDeleteImage();
@@ -1214,7 +1230,8 @@
         int albumPageTransition = transitions.get(
                 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
 
-        if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null) {
+        if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null
+                && mRecenterCameraOnResume) {
             // Generally, resuming the PhotoPage when in Camera should
             // reset to the capture mode to allow quick photo taking
             mCurrentIndex = 0;
@@ -1271,7 +1288,10 @@
         mActionBar.setDisplayOptions(
                 ((mSecureAlbum == null) && (mSetPathString != null)), false);
         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
-        if (mSecureAlbum == null) {
+        if (mBottomControls != null) {
+            mBottomControls.refresh();
+        }
+        if (mShowSpinner) {
             mActionBar.enableAlbumModeMenu(
                     GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
         }
@@ -1286,6 +1306,7 @@
         }
 
         mHasActivityResult = false;
+        mRecenterCameraOnResume = true;
         mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
     }
 
diff --git a/src/com/android/gallery3d/app/StitchingChangeListener.java b/src/com/android/gallery3d/app/StitchingChangeListener.java
index 901f379..980f145 100644
--- a/src/com/android/gallery3d/app/StitchingChangeListener.java
+++ b/src/com/android/gallery3d/app/StitchingChangeListener.java
@@ -16,11 +16,12 @@
 
 package com.android.gallery3d.app;
 
+import android.net.Uri;
 
 public interface StitchingChangeListener {
-    public void onFileAdded(String filePath);
+    public void onStitchingQueued(String filePath);
 
-    public void onFileRemoved(String filePath);
+    public void onStitchingResult(String filePath, Uri uri);
 
-    public void onProgressChanged(String filePath, int progress);
+    public void onStitchingProgress(String filePath, int progress);
 }
diff --git a/src/com/android/gallery3d/data/ImageCacheService.java b/src/com/android/gallery3d/data/ImageCacheService.java
index 0e79313..f10a7b3 100644
--- a/src/com/android/gallery3d/data/ImageCacheService.java
+++ b/src/com/android/gallery3d/data/ImageCacheService.java
@@ -35,7 +35,7 @@
     private static final String IMAGE_CACHE_FILE = "imgcache";
     private static final int IMAGE_CACHE_MAX_ENTRIES = 5000;
     private static final int IMAGE_CACHE_MAX_BYTES = 200 * 1024 * 1024;
-    private static final int IMAGE_CACHE_VERSION = 4;
+    private static final int IMAGE_CACHE_VERSION = 5;
 
     private BlobCache mCache;
 
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index 9d2040a..77b86b8 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -30,8 +30,6 @@
     public static final int TYPE_THUMBNAIL = 1;
     public static final int TYPE_MICROTHUMBNAIL = 2;
 
-    public static final int THUMBNAIL_TARGET_SIZE = 640;
-    public static final int MICROTHUMBNAIL_TARGET_SIZE = 200;
     public static final int CACHED_IMAGE_QUALITY = 95;
 
     public static final int IMAGE_READY = 0;
@@ -43,19 +41,17 @@
     private static final int BYTESBUFFE_POOL_SIZE = 4;
     private static final int BYTESBUFFER_SIZE = 200 * 1024;
 
-    private static final BitmapPool sMicroThumbPool =
-            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-            ? new BitmapPool(MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE, 16)
-            : null;
+    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;
 
-    private static final BytesBufferPool sMicroThumbBufferPool =
-            new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
-
     // TODO: fix default value for latlng and change this.
     public static final double INVALID_LATLNG = 0f;
 
@@ -125,9 +121,9 @@
     public static int getTargetSize(int type) {
         switch (type) {
             case TYPE_THUMBNAIL:
-                return THUMBNAIL_TARGET_SIZE;
+                return sThumbnailTargetSize;
             case TYPE_MICROTHUMBNAIL:
-                return MICROTHUMBNAIL_TARGET_SIZE;
+                return sMicrothumbnailTargetSize;
             default:
                 throw new RuntimeException(
                     "should only request thumb/microthumb from cache");
@@ -135,6 +131,9 @@
     }
 
     public static BitmapPool getMicroThumbPool() {
+        if (ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY && sMicroThumbPool == null) {
+            initializeMicroThumbPool();
+        }
         return sMicroThumbPool;
     }
 
@@ -145,4 +144,19 @@
     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/SecureAlbum.java b/src/com/android/gallery3d/data/SecureAlbum.java
index b841df7..382de5b 100644
--- a/src/com/android/gallery3d/data/SecureAlbum.java
+++ b/src/com/android/gallery3d/data/SecureAlbum.java
@@ -24,12 +24,13 @@
 import android.provider.MediaStore.Video;
 
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.app.StitchingChangeListener;
 import com.android.gallery3d.util.MediaSetUtils;
 
 import java.util.ArrayList;
 
 // This class lists all media items added by the client.
-public class SecureAlbum extends MediaSet {
+public class SecureAlbum extends MediaSet implements StitchingChangeListener {
     @SuppressWarnings("unused")
     private static final String TAG = "SecureAlbum";
     private static final String[] PROJECTION = {MediaColumns._ID};
@@ -42,6 +43,7 @@
     // The types of items in mAllItems. True is video and false is image.
     private ArrayList<Boolean> mAllItemTypes = new ArrayList<Boolean>();
     private ArrayList<Path> mExistingItems = new ArrayList<Path>();
+    private ArrayList<String> mStitchingFilePaths = new ArrayList<String>();
     private Context mContext;
     private DataManager mDataManager;
     private static final Uri[] mWatchUris =
@@ -60,6 +62,7 @@
         mUnlockItem = unlock;
         mShowUnlockItem = (!isCameraBucketEmpty(Images.Media.EXTERNAL_CONTENT_URI)
                 || !isCameraBucketEmpty(Video.Media.EXTERNAL_CONTENT_URI));
+        application.getStitchingProgressManager().addChangeListener(this);
     }
 
     public void addMediaItem(boolean isVideo, int id) {
@@ -178,4 +181,21 @@
     public boolean isLeafAlbum() {
         return true;
     }
+
+    @Override
+    public void onStitchingQueued(String filePath) {
+        mStitchingFilePaths.add(filePath);
+    }
+
+    @Override
+    public void onStitchingResult(String filePath, Uri uri) {
+        if (mStitchingFilePaths.remove(filePath)) {
+            int id = Integer.parseInt(uri.getLastPathSegment());
+            addMediaItem(false, id);
+        }
+    }
+
+    @Override
+    public void onStitchingProgress(String filePath, int progress) {
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 964c950..89ef417 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -1,37 +1,18 @@
 
 package com.android.gallery3d.filtershow;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Vector;
-
-import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.filters.*;
-import com.android.gallery3d.filtershow.imageshow.ImageBorder;
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
-import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
-import com.android.gallery3d.filtershow.imageshow.ImageZoom;
-import com.android.gallery3d.filtershow.presets.*;
-import com.android.gallery3d.filtershow.provider.SharedImageProvider;
-import com.android.gallery3d.filtershow.tools.SaveCopyTask;
-import com.android.gallery3d.filtershow.ui.ImageCurves;
-import com.android.gallery3d.R;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.renderscript.RenderScript;
 import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.ProgressDialog;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
+import android.net.Uri;
+import android.os.Bundle;
 import android.util.Log;
 import android.util.TypedValue;
-import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -40,20 +21,44 @@
 import android.view.View.OnTouchListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.ListView;
+import android.widget.SeekBar;
 import android.widget.ShareActionProvider;
 import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
-import android.widget.Toast;
+
+import com.android.gallery3d.R;
+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.ImageFilterRS;
+import com.android.gallery3d.filtershow.imageshow.ImageBorder;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.imageshow.ImageZoom;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.presets.ImagePresetBW;
+import com.android.gallery3d.filtershow.presets.ImagePresetBWBlue;
+import com.android.gallery3d.filtershow.presets.ImagePresetBWGreen;
+import com.android.gallery3d.filtershow.presets.ImagePresetBWRed;
+import com.android.gallery3d.filtershow.presets.ImagePresetOld;
+import com.android.gallery3d.filtershow.presets.ImagePresetSaturated;
+import com.android.gallery3d.filtershow.presets.ImagePresetXProcessing;
+import com.android.gallery3d.filtershow.provider.SharedImageProvider;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+import com.android.gallery3d.filtershow.ui.ImageCurves;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.Vector;
 
 @TargetApi(16)
 public class FilterShowActivity extends Activity implements OnItemClickListener,
         OnShareTargetSelectedListener {
 
+    private final PanelController mPanelController = new PanelController();
     private ImageLoader mImageLoader = null;
     private ImageShow mImageShow = null;
     private ImageCurves mImageCurves = null;
@@ -65,40 +70,31 @@
     private View mListBorders = null;
     private View mListGeometry = null;
     private View mListColors = null;
+    private View mListFilterButtons = null;
 
     private ImageButton mFxButton = null;
     private ImageButton mBorderButton = null;
     private ImageButton mGeometryButton = null;
     private ImageButton mColorsButton = null;
 
-    private ImageButton mVignetteButton = null;
-    private ImageButton mCurvesButtonRGB = null;
-    private ImageButton mSharpenButton = null;
-
-    private ImageButton mContrastButton = null;
-    private ImageButton mSaturationButton = null;
-    private ImageButton mTintButton = null;
-    private ImageButton mVibranceButton = null;
-    private ImageButton mExposureButton = null;
-    private ImageButton mShadowRecoveryButton = null;
-
     private static final int SELECT_PICTURE = 1;
     private static final String LOGTAG = "FilterShowActivity";
-    protected static final boolean ANIMATE_PANELS = false;
+    protected static final boolean ANIMATE_PANELS = true;
 
     private boolean mShowingHistoryPanel = false;
     private boolean mShowingImageStatePanel = false;
 
-    private Vector<ImageShow> mImageViews = new Vector<ImageShow>();
-    private Vector<View> mListViews = new Vector<View>();
-    private Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
-    private Vector<ImageButton> mColorsPanelButtons = new Vector<ImageButton>();
+    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;
 
     private boolean mSharingImage = false;
 
+    private WeakReference<ProgressDialog> mSavingProgressDialog;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -136,67 +132,60 @@
 
         mListFx = findViewById(R.id.fxList);
         mListBorders = findViewById(R.id.bordersList);
-        mListGeometry = findViewById(R.id.gemoetryList);
+        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);
-        mFxButton.setSelected(true);
 
-        mVignetteButton = (ImageButton) findViewById(R.id.vignetteButton);
-        mCurvesButtonRGB = (ImageButton) findViewById(R.id.curvesButtonRGB);
-        mSharpenButton = (ImageButton) findViewById(R.id.sharpenButton);
-        mVibranceButton = (ImageButton) findViewById(R.id.vibranceButton);
-        mContrastButton = (ImageButton) findViewById(R.id.contrastButton);
-        mSaturationButton = (ImageButton) findViewById(R.id.saturationButton);
-        mTintButton = (ImageButton) findViewById(R.id.tintButton);
-        mExposureButton = (ImageButton) findViewById(R.id.exposureButton);
-        mShadowRecoveryButton = (ImageButton) findViewById(R.id.shadowRecoveryButton);
+        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);
 
-        mColorsPanelButtons.add(mVignetteButton);
-        mColorsPanelButtons.add(mCurvesButtonRGB);
-        mColorsPanelButtons.add(mSharpenButton);
-        mColorsPanelButtons.add(mContrastButton);
-        mColorsPanelButtons.add(mSaturationButton);
-        mColorsPanelButtons.add(mTintButton);
-        mColorsPanelButtons.add(mVibranceButton);
-        mColorsPanelButtons.add(mExposureButton);
-        mColorsPanelButtons.add(mShadowRecoveryButton);
+        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.imageZoom));
 
-        // TODO: instead of click listeners, make the activity the single
-        // listener and do a dispatch in the listener callback method.
-        findViewById(R.id.showOriginalButton).setOnTouchListener(createOnTouchShowOriginalButton());
-        findViewById(R.id.straightenButton).setOnClickListener(createOnClickStraightenButton());
-        findViewById(R.id.cropButton).setOnClickListener(createOnClickCropButton());
-        findViewById(R.id.rotateButton).setOnClickListener(createOnClickRotateButton());
-        findViewById(R.id.flipButton).setOnClickListener(createOnClickFlipButton());
+        mPanelController.addPanel(mFxButton, mListFx, 0);
+        mPanelController.addPanel(mBorderButton, mListBorders, 1);
 
-        mVignetteButton.setOnClickListener(createOnClickVignetteButton());
-        mCurvesButtonRGB.setOnClickListener(createOnClickCurvesRGBButton());
+        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));
 
-        mSharpenButton.setOnClickListener(createOnClickSharpenButton());
-        mContrastButton.setOnClickListener(createOnClickContrastButton());
-        mSaturationButton.setOnClickListener(createOnClickSaturationButton());
+        mPanelController.addPanel(mColorsButton, mListColors, 3);
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.vignetteButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.curvesButtonRGB));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.sharpenButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.vibranceButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.contrastButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.saturationButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.tintButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.exposureButton));
+        mPanelController.addComponent(mColorsButton, findViewById(R.id.shadowRecoveryButton));
 
-        mTintButton.setOnClickListener(createOnClickTintButton());
-        mVibranceButton.setOnClickListener(createOnClickVibranceButton());
-        mExposureButton.setOnClickListener(createOnClickExposureButton());
-        mShadowRecoveryButton.setOnClickListener(createOnClickShadowRecoveryButton());
+        mPanelController.addView(findViewById(R.id.resetEffect));
+        mPanelController.addView(findViewById(R.id.applyEffect));
 
-        mFxButton.setOnClickListener(createOnClickFxButton());
-        mBorderButton.setOnClickListener(createOnClickBorderButton());
-        mGeometryButton.setOnClickListener(createOnClickGeometryButton());
-        mColorsButton.setOnClickListener(createOnClickColorsButton());
+        findViewById(R.id.compareWithOriginalImage).setOnTouchListener(createOnTouchShowOriginalButton());
 
         findViewById(R.id.resetOperationsButton).setOnClickListener(
                 createOnClickResetOperationsButton());
@@ -211,15 +200,15 @@
         fillListImages(listFilters);
         fillListBorders(listBorders);
 
-        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);
+        SeekBar seekBar = (SeekBar) findViewById(R.id.filterSeekBar);
+        seekBar.setMax(200);
+        mImageShow.setSeekBar(seekBar);
+        mPanelController.setRowPanel(findViewById(R.id.secondRowPanel));
+        mPanelController.setUtilityPanel(findViewById(R.id.filterButtonsList),
+                findViewById(R.id.compareWithOriginalImage),
+                findViewById(R.id.applyEffect));
+        mPanelController.setMasterImage(mImageShow);
+        mPanelController.setCurrentPanel(mFxButton);
 
         Intent intent = getIntent();
         String data = intent.getDataString();
@@ -231,6 +220,27 @@
         }
     }
 
+    private void showSavingProgress() {
+        ProgressDialog progress;
+        if (mSavingProgressDialog != null) {
+            progress = mSavingProgressDialog.get();
+            if (progress != null) {
+                progress.show();
+                return;
+            }
+        }
+        // TODO: Allow cancellation of the saving process
+        progress = ProgressDialog.show(this, "", getString(R.string.saving_image), true, false);
+        mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
+    }
+
+    private void hideSavingProgress() {
+        if (mSavingProgressDialog != null) {
+            ProgressDialog progress = mSavingProgressDialog.get();
+            if (progress != null) progress.dismiss();
+        }
+    }
+
     public void completeSaveImage(Uri saveUri) {
         if (mSharingImage && mSharedOutputFile != null) {
             // Image saved, we unblock the content provider
@@ -241,12 +251,14 @@
             getContentResolver().insert(uri, values);
         }
         setResult(RESULT_OK, new Intent().setData(saveUri));
+        hideSavingProgress();
         finish();
     }
 
     @Override
     public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
-        // First, let's tell the SharedImageProvider that it will need to wait for the image
+        // First, let's tell the SharedImageProvider that it will need to wait
+        // for the image
         Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
                 Uri.encode(mSharedOutputFile.getAbsolutePath()));
         ContentValues values = new ContentValues();
@@ -255,6 +267,7 @@
         mSharingImage = true;
 
         // Process and save the image in the background.
+        showSavingProgress();
         mImageShow.saveImage(this, mSharedOutputFile);
         return true;
     }
@@ -293,6 +306,7 @@
         return true;
     }
 
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.undoButton: {
@@ -395,6 +409,7 @@
 
     // //////////////////////////////////////////////////////////////////////////////
     // Some utility functions
+    // TODO: finish the cleanup.
 
     public void showOriginalViews(boolean value) {
         for (ImageShow views : mImageViews) {
@@ -467,6 +482,7 @@
             mShowingImageStatePanel = true;
             view.animate().setDuration(200).x(-viewList.getWidth())
                     .withLayer().withEndAction(new Runnable() {
+                        @Override
                         public void run() {
                             viewList.setAlpha(0);
                             viewList.setVisibility(View.VISIBLE);
@@ -499,6 +515,7 @@
             mShowingHistoryPanel = true;
             view.animate().setDuration(200).x(-viewList.getWidth())
                     .withLayer().withEndAction(new Runnable() {
+                        @Override
                         public void run() {
                             viewList.setAlpha(0);
                             viewList.setVisibility(View.VISIBLE);
@@ -535,350 +552,6 @@
     }
 
     // //////////////////////////////////////////////////////////////////////////////
-    // Now, let's deal with the bottom panel.
-
-    private OnClickListener createOnClickFxButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                hideListViews();
-                unselectBottomPanelButtons();
-                mImageShow.setVisibility(View.VISIBLE);
-                mListFx.setVisibility(View.VISIBLE);
-                mFxButton.setSelected(true);
-            }
-        };
-    }
-
-    private OnClickListener createOnClickBorderButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                hideListViews();
-                unselectBottomPanelButtons();
-                mImageBorders.setVisibility(View.VISIBLE);
-                mListBorders.setVisibility(View.VISIBLE);
-                mBorderButton.setSelected(true);
-            }
-        };
-    }
-
-    private OnClickListener createOnClickGeometryButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                hideListViews();
-                unselectBottomPanelButtons();
-                mImageStraighten.setVisibility(View.VISIBLE);
-                mListGeometry.setVisibility(View.VISIBLE);
-                mGeometryButton.setSelected(true);
-
-                if (ANIMATE_PANELS) {
-                    mListGeometry.setX(mListGeometry.getWidth());
-                    mListGeometry.animate().setDuration(200).x(0).withLayer().start();
-                }
-            }
-        };
-    }
-
-    private OnClickListener createOnClickColorsButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                hideListViews();
-                unselectBottomPanelButtons();
-                mListColors.setVisibility(View.VISIBLE);
-                mImageShow.setVisibility(View.VISIBLE);
-                mColorsButton.setSelected(true);
-
-                if (ANIMATE_PANELS) {
-                    View view = findViewById(R.id.listColorsFx);
-                    view.setX(mListColors.getWidth());
-                    view.animate().setDuration(200).x(0).withLayer().start();
-                }
-            }
-        };
-    }
-
-    // //////////////////////////////////////////////////////////////////////////////
-    // Geometry sub-panel
-
-    private OnClickListener createOnClickStraightenButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageStraighten.setVisibility(View.VISIBLE);
-                mImageStraighten.showToast("Straighten", true);
-            }
-        };
-    }
-
-    private OnClickListener createOnClickCropButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.showToast("Crop", true);
-            }
-        };
-    }
-
-    private OnClickListener createOnClickRotateButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.showToast("Rotate", true);
-            }
-        };
-    }
-
-    private OnClickListener createOnClickFlipButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.showToast("Flip", true);
-            }
-        };
-    }
-
-    // //////////////////////////////////////////////////////////////////////////////
-    // Filters sub-panel
-
-    private OnClickListener createOnClickVignetteButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Vignette");
-                if (filter == null) {
-                    ImageFilterVignette vignette = new ImageFilterVignette();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(vignette);
-                    copy.setHistoryName(vignette.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Vignette");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mVignetteButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickCurvesRGBButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageCurves.setVisibility(View.VISIBLE);
-                unselectPanelButtons(mColorsPanelButtons);
-                mCurvesButtonRGB.setSelected(true);
-                mImageCurves.setUseRed(true);
-                mImageCurves.setUseGreen(true);
-                mImageCurves.setUseBlue(true);
-                mImageCurves.reloadCurve();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickSharpenButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageZoom.setVisibility(View.VISIBLE);
-                mImageZoom.setShowControls(true);
-                ImagePreset preset = mImageZoom.getImagePreset();
-                ImageFilter filter = preset.getFilter("Sharpen");
-                if (filter == null) {
-                    ImageFilterSharpen sharpen = new ImageFilterSharpen();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(sharpen);
-                    copy.setHistoryName(sharpen.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Sharpen");
-                    mImageZoom.setImagePreset(copy);
-                }
-                mImageZoom.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mSharpenButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickContrastButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Contrast");
-                if (filter == null) {
-                    ImageFilterContrast contrast = new ImageFilterContrast();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(contrast);
-                    copy.setHistoryName(contrast.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Contrast");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mContrastButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickSaturationButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Saturated");
-                if (filter == null) {
-                    ImageFilterSaturated sat = new ImageFilterSaturated();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(sat);
-                    copy.setHistoryName(sat.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Saturated");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mSaturationButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickTintButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Hue");
-                if (filter == null) {
-                    ImageFilterHue contrast = new ImageFilterHue();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(contrast);
-                    copy.setHistoryName(contrast.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Hue");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mTintButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickVibranceButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Vibrance");
-                if (filter == null) {
-                    ImageFilterVibrance contrast = new ImageFilterVibrance();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(contrast);
-                    copy.setHistoryName(contrast.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Vibrance");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mVibranceButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickExposureButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Exposure");
-                if (filter == null) {
-                    ImageFilterExposure bright = new ImageFilterExposure();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(bright);
-                    copy.setHistoryName(bright.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Exposure");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mExposureButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    private OnClickListener createOnClickShadowRecoveryButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
-                mImageShow.setShowControls(true);
-                ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Shadows");
-                if (filter == null) {
-                    ImageFilterShadows contrast = new ImageFilterShadows();
-                    ImagePreset copy = new ImagePreset(preset);
-                    copy.add(contrast);
-                    copy.setHistoryName(contrast.getName());
-                    copy.setIsFx(false);
-                    filter = copy.getFilter("Shadows");
-                    mImageShow.setImagePreset(copy);
-                }
-                mImageShow.setCurrentFilter(filter);
-                unselectPanelButtons(mColorsPanelButtons);
-                mShadowRecoveryButton.setSelected(true);
-                invalidateViews();
-            }
-        };
-    }
-
-    // //////////////////////////////////////////////////////////////////////////////
 
     public float getPixelsFromDip(float value) {
         Resources r = getResources();
@@ -930,6 +603,7 @@
                 SELECT_PICTURE);
     }
 
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         Log.v(LOGTAG, "onActivityResult");
         if (resultCode == RESULT_OK) {
@@ -941,11 +615,7 @@
     }
 
     public void saveImage() {
-        Toast toast = Toast.makeText(getBaseContext(), getString(R.string.saving_image),
-                Toast.LENGTH_SHORT);
-        toast.setGravity(Gravity.CENTER, 0, 0);
-        toast.show();
-
+        showSavingProgress();
         mImageShow.saveImage(this, null);
     }
 
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
new file mode 100644
index 0000000..f9a1f1d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/PanelController.java
@@ -0,0 +1,474 @@
+
+package com.android.gallery3d.filtershow;
+
+import android.text.Html;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewPropertyAnimator;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterContrast;
+import com.android.gallery3d.filtershow.filters.ImageFilterExposure;
+import com.android.gallery3d.filtershow.filters.ImageFilterHue;
+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.ImageFilterVibrance;
+import com.android.gallery3d.filtershow.filters.ImageFilterVignette;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+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;
+
+    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 View mView;
+        private final View mCompareView;
+        private final TextView mTextView;
+        private boolean mSelected = false;
+        private String mEffectName = null;
+        private int mParameterValue = 0;
+
+        public UtilityPanel(View view, View compareView, View textView) {
+            mView = view;
+            mCompareView = compareView;
+            mTextView = (TextView) textView;
+        }
+
+        public boolean selected() {
+            return mSelected;
+        }
+
+        public void onNewValue(int value) {
+            mParameterValue = value;
+            updateText();
+        }
+
+        public void setGeometryEffect(boolean isGeometryEffect) {
+            if (isGeometryEffect) {
+                mCompareView.setVisibility(View.INVISIBLE);
+            } else {
+                mCompareView.setVisibility(View.VISIBLE);
+            }
+        }
+
+        public void setEffectName(String effectName) {
+            mEffectName = effectName;
+            updateText();
+        }
+
+        public void updateText() {
+            mTextView.setText(Html.fromHtml("Apply" + "<br/><small>" + mEffectName + "<br/>"
+                    + mParameterValue + "</small>"));
+        }
+
+        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;
+
+    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 onNewValue(int value) {
+        mUtilityPanel.onNewValue(value);
+    }
+
+    public void setCurrentPanel(View panel) {
+        showPanel(panel);
+    }
+
+    public void setRowPanel(View rowPanel) {
+        mRowPanel = rowPanel;
+    }
+
+    public void setUtilityPanel(View utilityPanel, View compareView, View textView) {
+        mUtilityPanel = new UtilityPanel(utilityPanel, compareView, textView);
+    }
+
+    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(filter.getName());
+        copy.setIsFx(false);
+        mMasterImage.setImagePreset(copy);
+        return filter;
+    }
+
+    public void ensureFilter(String name) {
+        ImagePreset preset = getImagePreset();
+        ImageFilter filter = preset.getFilter(name);
+        if (filter == null && name.equalsIgnoreCase("Vignette")) {
+            filter = setImagePreset(new ImageFilterVignette(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Sharpen")) {
+            filter = setImagePreset(new ImageFilterSharpen(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Contrast")) {
+            filter = setImagePreset(new ImageFilterContrast(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Saturated")) {
+            filter = setImagePreset(new ImageFilterSaturated(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Hue")) {
+            filter = setImagePreset(new ImageFilterHue(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Exposure")) {
+            filter = setImagePreset(new ImageFilterExposure(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Vibrance")) {
+            filter = setImagePreset(new ImageFilterVibrance(), name);
+        }
+        if (filter == null && name.equalsIgnoreCase("Shadows")) {
+            filter = setImagePreset(new ImageFilterShadows(), name);
+        }
+        mMasterImage.setCurrentFilter(filter);
+    }
+
+    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 (mCurrentImage != null) {
+            mCurrentImage.unselect();
+        }
+
+        switch (view.getId()) {
+            case R.id.straightenButton: {
+                mCurrentImage = showImageView(R.id.imageStraighten);
+                mUtilityPanel.setEffectName("Straighten");
+                mUtilityPanel.setGeometryEffect(true);
+                break;
+            }
+            case R.id.cropButton: {
+                mCurrentImage = showImageView(R.id.imageShow);
+                mUtilityPanel.setEffectName("Crop");
+                mUtilityPanel.setGeometryEffect(true);
+                break;
+            }
+            case R.id.rotateButton: {
+                mCurrentImage = showImageView(R.id.imageShow);
+                mUtilityPanel.setEffectName("Rotate");
+                mUtilityPanel.setGeometryEffect(true);
+                break;
+            }
+            case R.id.flipButton: {
+                mCurrentImage = showImageView(R.id.imageShow);
+                mUtilityPanel.setEffectName("Flip");
+                mUtilityPanel.setGeometryEffect(true);
+                break;
+            }
+            case R.id.vignetteButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Vignette");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Vignette");
+                break;
+            }
+            case R.id.curvesButtonRGB: {
+                ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves);
+                mUtilityPanel.setEffectName("Curves");
+                mUtilityPanel.setGeometryEffect(true);
+                curves.setUseRed(true);
+                curves.setUseGreen(true);
+                curves.setUseBlue(true);
+                curves.reloadCurve();
+                mCurrentImage = curves;
+                break;
+            }
+            case R.id.sharpenButton: {
+                mCurrentImage = showImageView(R.id.imageZoom).setShowControls(true);
+                mUtilityPanel.setEffectName("Sharpen");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Sharpen");
+                break;
+            }
+            case R.id.contrastButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Contrast");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Contrast");
+                break;
+            }
+            case R.id.saturationButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Saturated");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Saturated");
+                break;
+            }
+            case R.id.tintButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Hue");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Hue");
+                break;
+            }
+            case R.id.exposureButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Exposure");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Exposure");
+                break;
+            }
+            case R.id.vibranceButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Vibrance");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Vibrance");
+                break;
+            }
+            case R.id.shadowRecoveryButton: {
+                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
+                mUtilityPanel.setEffectName("Shadows");
+                mUtilityPanel.setGeometryEffect(false);
+                ensureFilter("Shadows");
+                break;
+            }
+            case R.id.resetEffect: {
+                mCurrentImage.resetParameter();
+                break;
+            }
+            case R.id.applyEffect: {
+                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
index 93d57ed..e04c366 100644
--- a/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
+++ b/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
@@ -1,25 +1,24 @@
 
 package com.android.gallery3d.filtershow.cache;
 
-import java.nio.ByteBuffer;
+import android.graphics.Bitmap;
 
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-import android.graphics.Bitmap;
-import android.util.Log;
+import java.nio.ByteBuffer;
 
 public class BitmapCache {
 
     private static final String LOGTAG = "BitmapCache";
     static int mNbItems = 20;
-    private Bitmap[] mBitmaps = new Bitmap[mNbItems];
-    private Object[] mKeys = new Object[mNbItems];
-    private long[] mIndices = new long[mNbItems];
-    private boolean[] mBusyStatus = new boolean[mNbItems];
+    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 Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+    private final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
     private long mIndex = 0;
 
     public void setOriginalBitmap(Bitmap original) {
@@ -57,8 +56,7 @@
     public Bitmap put(ImagePreset preset, int pos) {
         mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
         Bitmap bitmap = mBitmaps[pos];
-
-        preset.apply(bitmap);
+        bitmap = preset.apply(bitmap);
         mKeys[pos] = preset;
         mIndices[pos] = mIndex++;
         return bitmap;
@@ -80,7 +78,7 @@
         ImagePreset preset = (ImagePreset) mKeys[pos];
         mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
         Bitmap bitmap = mBitmaps[pos];
-        preset.apply(bitmap);
+        bitmap = preset.apply(bitmap);
         mIndices[pos] = mIndex++;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
index 8acb539..361190b 100644
--- a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
+++ b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
@@ -14,7 +14,7 @@
     private final static int COMPUTE_PRESET = 1;
 
     private Handler mProcessingHandler = null;
-    private Handler mUIHandler = new Handler() {
+    private final Handler mUIHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -41,13 +41,14 @@
         return false;
     }
 
-    public DelayedPresetCache(int size) {
-        super(size);
+    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;
diff --git a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
index 1ba8356..67bd49b 100644
--- a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
+++ b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
@@ -1,23 +1,23 @@
 
 package com.android.gallery3d.filtershow.cache;
 
-import java.util.Vector;
-
 import android.graphics.Bitmap;
-import android.util.Log;
 
 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 Vector<ImageShow> mObservers = new Vector<ImageShow>();
-    private Vector<CachedPreset> mCache = new Vector<CachedPreset>();
+    private final Vector<ImageShow> mObservers = new Vector<ImageShow>();
+    private final Vector<CachedPreset> mCache = new Vector<CachedPreset>();
     private int mCacheSize = 1;
-    private Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
+    private final Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
     private long mGlobalAge = 0;
+    private ImageLoader mLoader = null;
 
     protected class CachedPreset {
         private Bitmap mBitmap = null;
@@ -34,10 +34,12 @@
         }
     }
 
-    public DirectPresetCache(int size) {
+    public DirectPresetCache(ImageLoader loader, int size) {
+        mLoader = loader;
         mCacheSize = size;
     }
 
+    @Override
     public void setOriginalBitmap(Bitmap bitmap) {
         mOriginalBitmap = bitmap;
         notifyObservers();
@@ -50,6 +52,7 @@
         }
     }
 
+    @Override
     public void addObserver(ImageShow observer) {
         if (!mObservers.contains(observer)) {
             mObservers.add(observer);
@@ -66,6 +69,7 @@
         return null;
     }
 
+    @Override
     public Bitmap get(ImagePreset preset) {
         // Log.v(LOGTAG, "get preset " + preset.name() + " : " + preset);
         CachedPreset cache = getCachedPreset(preset);
@@ -77,6 +81,7 @@
         return null;
     }
 
+    @Override
     public void reset(ImagePreset preset) {
         CachedPreset cache = getCachedPreset(preset);
         if (cache != null && !cache.mBusy) {
@@ -120,10 +125,16 @@
     protected void compute(CachedPreset cache) {
         cache.mBitmap = null;
         cache.mBitmap = mOriginalBitmap.copy(mBitmapConfig, true);
-        cache.mPreset.apply(cache.mBitmap);
+        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);
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index 9944f5f..8d8024e 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -35,16 +35,16 @@
 public class ImageLoader {
 
     private static final String LOGTAG = "ImageLoader";
-    private Vector<ImageShow> mListeners = new Vector<ImageShow>();
+    private final Vector<ImageShow> mListeners = new Vector<ImageShow>();
     private Bitmap mOriginalBitmapSmall = null;
     private Bitmap mOriginalBitmapLarge = null;
     private Bitmap mBackgroundBitmap = null;
     private Bitmap mFullOriginalBitmap = null;
     private Bitmap mSaveCopy = null;
 
-    private Cache mCache = new DelayedPresetCache(30);
-    private Cache mHiresCache = new DelayedPresetCache(2);
-    private ZoomCache mZoomCache = new ZoomCache();
+    private Cache mCache = null;
+    private Cache mHiresCache = null;
+    private final ZoomCache mZoomCache = new ZoomCache();
 
     private int mOrientation = 0;
     private HistoryAdapter mAdapter = null;
@@ -57,6 +57,8 @@
 
     public ImageLoader(Context context) {
         mContext = context;
+        mCache = new DelayedPresetCache(this, 30);
+        mHiresCache = new DelayedPresetCache(this, 2);
     }
 
     public void loadBitmap(Uri uri) {
@@ -230,7 +232,7 @@
             if (bmp != null) {
                 // TODO: this workaround for RS might not be needed ultimately
                 Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
-                imagePreset.apply(bmp2);
+                bmp2 = imagePreset.apply(bmp2);
                 mZoomCache.setImage(imagePreset, bounds, bmp2);
                 return bmp2;
             }
@@ -289,6 +291,8 @@
             // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
             // exist)
             mSaveCopy = mFullOriginalBitmap;
+            preset.setIsHighQuality(true);
+            preset.setScaleFactor(1.0f);
             ProcessedBitmap processedBitmap = new ProcessedBitmap(mSaveCopy, preset);
             new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index c039fce..662e8ed 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -2,7 +2,6 @@
 package com.android.gallery3d.filtershow.filters;
 
 import android.graphics.Bitmap;
-import android.util.Log;
 
 public class ImageFilter implements Cloneable {
 
@@ -26,8 +25,9 @@
         return mName;
     }
 
-    public void apply(Bitmap bitmap) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         // do nothing here, subclasses will implement filtering here
+        return bitmap;
     }
 
     public int getParameter() {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
index bc3dd09..bdbd1d5 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
@@ -11,10 +11,12 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
 
-    public void apply(Bitmap bitmap) {
+    @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
index 7368be5..b3c8a9e 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
@@ -11,10 +11,12 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
 
-    public void apply(Bitmap bitmap) {
+    @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
index f4e6c61..e3963dc 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
@@ -11,10 +11,12 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
 
-    public void apply(Bitmap bitmap) {
+    @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
index bb7d661..19b3c6b 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
@@ -11,10 +11,12 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
 
-    public void apply(Bitmap bitmap) {
+    @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 7df7a2b..67904c6 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
@@ -1,13 +1,10 @@
 
 package com.android.gallery3d.filtershow.filters;
 
-import com.android.gallery3d.R;
-
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
 
 public class ImageFilterBorder extends ImageFilter {
     Drawable mNinePatch = null;
@@ -24,6 +21,7 @@
         mNinePatch = ninePatch;
     }
 
+    @Override
     public boolean same(ImageFilter filter) {
         boolean isBorderFilter = super.same(filter);
         if (!isBorderFilter) {
@@ -41,9 +39,10 @@
         mNinePatch = ninePatch;
     }
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         if (mNinePatch == null) {
-            return;
+            return bitmap;
         }
 
         int w = bitmap.getWidth();
@@ -53,5 +52,6 @@
         Canvas canvas = new Canvas(bitmap);
         mNinePatch.setBounds(bounds);
         mNinePatch.draw(canvas);
+        return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
index b3ef74d..5117f60 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
@@ -11,11 +11,13 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         float p = mParameter;
         float value = p;
         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 5de8ff4..01b280b 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
@@ -1,16 +1,15 @@
 
 package com.android.gallery3d.filtershow.filters;
 
-import com.android.gallery3d.filtershow.ui.Spline;
-
 import android.graphics.Bitmap;
-import android.util.Log;
+
+import com.android.gallery3d.filtershow.ui.Spline;
 
 public class ImageFilterCurves extends ImageFilter {
 
     private static final String LOGTAG = "ImageFilterCurves";
 
-    private float[] mCurve = new float[256];
+    private final float[] mCurve = new float[256];
 
     private boolean mUseRed = true;
     private boolean mUseGreen = true;
@@ -49,6 +48,7 @@
         }
     }
 
+    @Override
     public boolean same(ImageFilter filter) {
         boolean isCurveFilter = super.same(filter);
         if (!isCurveFilter) {
@@ -69,8 +69,8 @@
         }
     }
 
-    public void apply(Bitmap bitmap) {
-
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int[] redGradient = null;
         if (mUseRed) {
             redGradient = new int[256];
@@ -89,6 +89,7 @@
 
         nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
                 redGradient, greenGradient, blueGradient);
+        return bitmap;
     }
 
     public void setSpline(Spline spline) {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
index 85b6e4f..ee87413 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
@@ -12,11 +12,12 @@
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
 
     @Override
-    public void apply(Bitmap bitmap) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         int p = mParameter;
         float value = p;
         nativeApplyFilter(bitmap, w, h, value);
+        return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
index d27dd34..4fbd086 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
@@ -10,8 +10,6 @@
 import android.graphics.Paint.Style;
 import android.graphics.Shader.TileMode;
 
-import com.android.gallery3d.filtershow.ui.Spline;
-
 public class ImageFilterGradient extends ImageFilter {
 
     private Bitmap mGradientBitmap = null;
@@ -50,8 +48,8 @@
         mPositions = positions;
     }
 
-    public void apply(Bitmap bitmap) {
-
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         createGradient();
         int[] gradient = new int[256];
         int[] redGradient = new int[256];
@@ -66,6 +64,7 @@
         }
         nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
                 redGradient, greenGradient, blueGradient);
+        return bitmap;
     }
 
     public void createGradient() {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
index 874e7ec..6f6f9e8 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
@@ -20,7 +20,8 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float []matrix);
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         float p = mParameter;
@@ -29,5 +30,7 @@
         cmatrix.setHue(value);
 
         nativeApplyFilter(bitmap, w, h, cmatrix.getMatrix());
+
+        return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index ab2d304..ab92e4e 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -31,9 +31,10 @@
         mOutPixelsAllocation.copyTo(bitmap);
     }
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         if (bitmap == null) {
-            return;
+            return bitmap;
         }
         try {
             prepare(bitmap);
@@ -45,6 +46,7 @@
         } catch (android.renderscript.RSRuntimeException e) {
             Log.e(LOGTAG, "RS runtime exception ? " + e);
         }
+        return bitmap;
     }
 
     public static RenderScript getRenderScriptContext() {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
index d3db441..9047056 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -11,11 +11,13 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation);
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         int p = mParameter;
         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 39c9506..9b379a1 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
@@ -11,8 +11,8 @@
     private final float MID = .5f;
     private final float HIGHLIGHT = .9f;
 
-    private float []baseX = {0f,SHADOW,MID,HIGHLIGHT,1f};
-    private float []baseY = {0f,SHADOW,MID,HIGHLIGHT,1f};
+    private final float []baseX = {0f,SHADOW,MID,HIGHLIGHT,1f};
+    private final float []baseY = {0f,SHADOW,MID,HIGHLIGHT,1f};
 
     public ImageFilterShadows() {
         mName = "Shadows";
@@ -50,12 +50,14 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short []valMap);
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         float p = mParameter;
         baseY[1] = (float)(SHADOW*Math.pow(4, mParameter/100.));
 
         nativeApplyFilter(bitmap, w, h, calcMap());
+        return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
index 483d901..db85292 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
@@ -1,16 +1,13 @@
 
 package com.android.gallery3d.filtershow.filters;
 
-import java.nio.ByteBuffer;
-
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 
 public class ImageFilterStraighten extends ImageFilter {
-    private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+    private final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
     private float mRotation;
     private float mZoomFactor;
 
@@ -39,7 +36,8 @@
         mZoomFactor = zoomFactor;
     }
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         // 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?)
@@ -64,6 +62,7 @@
         temp.recycle();
         temp = null;
         pixels = null;
+        return bitmap;
     }
 
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
index fd437ee..8281573 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -12,11 +12,13 @@
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
 
     @Override
-    public void apply(Bitmap bitmap) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         int p = mParameter;
         float value = p;
         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 24699a2..4c43410 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -11,11 +11,14 @@
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
 
-    public void apply(Bitmap bitmap) {
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         float p = mParameter;
         float value = p / 100.0f;
         nativeApplyFilter(bitmap, w, h, value);
+
+        return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 2a660d4..da38d3b 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -1,36 +1,33 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
-import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.ImageStateAdapter;
-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.SliderListener;
-import com.android.gallery3d.filtershow.ui.SliderController;
-import com.android.gallery3d.R;
-import com.android.gallery3d.R.id;
-import com.android.gallery3d.R.layout;
-
-import java.io.File;
-
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.net.Uri;
 import android.os.Handler;
-import android.os.Message;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.MeasureSpec;
 import android.widget.ArrayAdapter;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
 
-public class ImageShow extends View implements SliderListener {
+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 SliderListener, OnSeekBarChangeListener {
 
     private static final String LOGTAG = "ImageShow";
 
@@ -64,8 +61,47 @@
     protected float mTouchX = 0;
     protected float mTouchY = 0;
 
-    private Handler mHandler = new Handler();
+    private SeekBar mSeekBar = null;
+    private PanelController mController = null;
 
+    private final Handler mHandler = new Handler();
+
+    public void select() {
+        if (getCurrentFilter() != null) {
+            int parameter = getCurrentFilter().getParameter();
+            updateSeekBar(parameter);
+        }
+    }
+
+    public void updateSeekBar(int parameter) {
+        if (mSeekBar == null) {
+            return;
+        }
+        int progress = parameter + 100;
+        mSeekBar.setProgress(progress);
+        if (getPanelController() != null) {
+            getPanelController().onNewValue(parameter);
+        }
+    }
+
+    public void unselect() {
+
+    }
+
+    public void resetParameter() {
+        onNewValue(0);
+        mSliderController.reset();
+    }
+
+    public void setPanelController(PanelController controller) {
+        mController = controller;
+    }
+
+    public PanelController getPanelController() {
+        return mController;
+    }
+
+    @Override
     public void onNewValue(int value) {
         if (getCurrentFilter() != null) {
             getCurrentFilter().setParameter(value);
@@ -74,15 +110,21 @@
             mImageLoader.resetImageForPreset(getImagePreset(), this);
             getImagePreset().fillImageStateAdapter(mImageStateAdapter);
         }
+        if (getPanelController() != null) {
+            getPanelController().onNewValue(value);
+        }
+        updateSeekBar(value);
         invalidate();
     }
 
+    @Override
     public void onTouchDown(float x, float y) {
         mTouchX = x;
         mTouchY = y;
         invalidate();
     }
 
+    @Override
     public void onTouchUp() {
     }
 
@@ -102,6 +144,7 @@
                 R.id.rowTextView);
     }
 
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -110,6 +153,11 @@
         mSliderController.setHeight(parentHeight);
     }
 
+    public void setSeekBar(SeekBar seekBar) {
+        mSeekBar = seekBar;
+        mSeekBar.setOnSeekBarChangeListener(this);
+    }
+
     public void setCurrentFilter(ImageFilter filter) {
         mCurrentFilter = filter;
     }
@@ -180,6 +228,7 @@
         }
     }
 
+    @Override
     public void onDraw(Canvas canvas) {
         drawBackground(canvas);
         getFilteredImage();
@@ -262,8 +311,18 @@
         }
     }
 
-    public void setShowControls(boolean value) {
+    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() {
@@ -324,6 +383,7 @@
         mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
     }
 
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         super.onTouchEvent(event);
         mSliderController.onTouchEvent(event);
@@ -368,4 +428,21 @@
         mImageRotation = imageRotation;
         mImageRotationZoomFactor = imageRotationZoomFactor;
     }
+
+    @Override
+    public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
+        onNewValue(progress - 100);
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar arg0) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar arg0) {
+        // TODO Auto-generated method stub
+
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
index debaec7..4fdf830 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
@@ -4,6 +4,7 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 
+import com.android.gallery3d.filtershow.PanelController;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
@@ -26,18 +27,22 @@
         mMasterImageShow = master;
     }
 
+    @Override
     public ImagePreset getImagePreset() {
         return mMasterImageShow.getImagePreset();
     }
 
+    @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();
     }
@@ -46,20 +51,35 @@
         mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor);
     }
 
+    @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/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
index eb034bf..99aa389 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -1,26 +1,24 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
 public class ImageStraighten extends ImageSlave {
     private float mImageRotation = 0;
     private float mImageRotationZoomFactor = 0;
 
-    private float mMinAngle = -45;
-    private float mMaxAngle = 45;
+    private final float mMinAngle = -45;
+    private final float mMaxAngle = 45;
     private float mBaseAngle = 0;
     private float mAngle = 0;
     private float mCenterX;
@@ -69,6 +67,7 @@
     public void setActionUp() {
         mMode = MODES.UP;
         updatePreset();
+        invalidate();
     }
 
     public void setNoAction() {
@@ -81,6 +80,16 @@
         setImagePreset(copy);
     }
 
+    @Override
+    public void resetParameter() {
+        super.resetParameter();
+        mImageRotation = 0;
+        mAngle = 0;
+        updatePreset();
+        invalidate();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getActionMasked()) {
             case (MotionEvent.ACTION_DOWN):
@@ -97,6 +106,9 @@
         }
         mImageRotation = mAngle;
         updateAngle();
+        if (getPanelController() != null) {
+            getPanelController().onNewValue((int) mImageRotation);
+        }
         invalidate();
         return true;
     }
@@ -124,11 +136,16 @@
 
     // ///////////////////////////////////////////////////////////////////////////
 
+    @Override
     public void onNewValue(int value) {
         mImageRotation = value;
+        if (getPanelController() != null) {
+            getPanelController().onNewValue(value);
+        }
         invalidate();
     }
 
+    @Override
     public void onDraw(Canvas canvas) {
         mCenterX = getWidth() / 2;
         mCenterY = getHeight() / 2;
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index 69b3f09..c0c3103 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -1,8 +1,6 @@
 
 package com.android.gallery3d.filtershow.presets;
 
-import java.util.Vector;
-
 import android.graphics.Bitmap;
 import android.graphics.RectF;
 import android.util.Log;
@@ -12,6 +10,8 @@
 import com.android.gallery3d.filtershow.filters.ImageFilterStraighten;
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
 
+import java.util.Vector;
+
 public class ImagePreset {
 
     private static final String LOGTAG = "ImagePreset";
@@ -32,6 +32,9 @@
     protected boolean mVerticalFlip = false;
     protected RectF mCrop = null;
 
+    private float mScaleFactor = 1.0f;
+    private boolean mIsHighQuality = false;
+
     public ImagePreset() {
         setup();
     }
@@ -50,6 +53,9 @@
 
         mStraightenRotate = source.mStraightenRotate;
         mStraightenZoom = source.mStraightenZoom;
+
+        mScaleFactor = source.mScaleFactor;
+        mIsHighQuality = source.mIsHighQuality;
     }
 
     public void setStraightenRotation(float rotate, float zoom) {
@@ -57,7 +63,7 @@
         mStraightenZoom = zoom;
     }
 
-    private Bitmap applyGeometry(Bitmap original) {
+    private Bitmap applyGeometry(Bitmap original, float scaleFactor, boolean highQuality) {
         Bitmap bitmap = original;
 
         if (mFullRotate != FullRotate.ZERO) {
@@ -67,7 +73,7 @@
         if (mStraightenRotate != 0) {
             // TODO: keep the instances around
             ImageFilter straighten = new ImageFilterStraighten(mStraightenRotate, mStraightenZoom);
-            straighten.apply(bitmap);
+            bitmap = straighten.apply(bitmap, scaleFactor, highQuality);
             straighten = null;
         }
 
@@ -154,7 +160,7 @@
 
     public Bitmap apply(Bitmap original) {
         // First we apply any transform -- 90 rotate, flip, straighten, crop
-        Bitmap bitmap = applyGeometry(original);
+        Bitmap bitmap = applyGeometry(original, mScaleFactor, mIsHighQuality);
 
         // TODO -- apply borders separately
         ImageFilter borderFilter = null;
@@ -164,11 +170,11 @@
                 // TODO don't use the name as an id
                 borderFilter = filter;
             } else {
-                filter.apply(bitmap);
+                bitmap = filter.apply(bitmap, mScaleFactor, mIsHighQuality);
             }
         }
         if (borderFilter != null) {
-            borderFilter.apply(bitmap);
+            bitmap = borderFilter.apply(bitmap, mScaleFactor, mIsHighQuality);
         }
         if (mEndPoint != null) {
             mEndPoint.updateFilteredImage(bitmap);
@@ -185,4 +191,19 @@
         imageStateAdapter.notifyDataSetChanged();
     }
 
+    public float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    public boolean isHighQuality() {
+        return mIsHighQuality;
+    }
+
+    public void setIsHighQuality(boolean value) {
+        mIsHighQuality = value;
+    }
+
+    public void setScaleFactor(float value) {
+        mScaleFactor = value;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java b/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java
index 2e23b92..24e38fb 100644
--- a/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java
+++ b/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java
@@ -6,13 +6,13 @@
 
 public class ProcessedBitmap {
     private Bitmap mBitmap;
-    private ImagePreset mPreset;
+    private final ImagePreset mPreset;
     public ProcessedBitmap(Bitmap bitmap, ImagePreset preset) {
         mBitmap = bitmap;
         mPreset = preset;
     }
     public Bitmap apply() {
-        mPreset.apply(mBitmap);
+        mBitmap = mPreset.apply(mBitmap);
         return mBitmap;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
index 3da058b..fb18bcf 100644
--- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -1,28 +1,22 @@
 
 package com.android.gallery3d.filtershow.ui;
 
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.imageshow.ImageSlave;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-import com.android.gallery3d.filtershow.ui.ControlPoint;
-import com.android.gallery3d.filtershow.ui.Spline;
-import com.android.gallery3d.R;
-
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.os.Message;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.PopupMenu;
 import android.widget.Toast;
 
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
+import com.android.gallery3d.filtershow.imageshow.ImageSlave;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
 public class ImageCurves extends ImageSlave {
 
     private static final String LOGTAG = "ImageCurves";
@@ -47,6 +41,7 @@
         resetCurve();
     }
 
+    @Override
     public boolean showTitle() {
         return false;
     }
@@ -77,6 +72,12 @@
         }
     }
 
+    @Override
+    public void resetParameter() {
+        super.resetParameter();
+        resetCurve();
+    }
+
     public void resetCurve() {
         mSpline = new Spline();
 
@@ -87,6 +88,7 @@
         }
     }
 
+    @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderController.java b/src/com/android/gallery3d/filtershow/ui/SliderController.java
index f2f0df3..99f66b1 100644
--- a/src/com/android/gallery3d/filtershow/ui/SliderController.java
+++ b/src/com/android/gallery3d/filtershow/ui/SliderController.java
@@ -5,8 +5,6 @@
 import android.graphics.Paint;
 import android.view.MotionEvent;
 
-import android.util.Log;
-
 public class SliderController {
     private static final String LOGTAG = "SliderController";
 
@@ -22,7 +20,7 @@
 
     private String mToast = null;
 
-    private Paint mPaint = new Paint();
+    private final Paint mPaint = new Paint();
 
     private SliderListener mListener = null;
 
@@ -37,7 +35,6 @@
         if (mMode == MODES.NONE || mMode == MODES.UP) {
             return;
         }
-        drawToast(canvas);
     }
 
     public void drawToast(Canvas canvas) {
@@ -62,7 +59,7 @@
     }
 
     protected int computeValue() {
-        int delta = (int) (100 * (getCurrentX() - getCenterX()) / (float) getWidth());
+        int delta = (int) (100 * (getCurrentX() - getCenterX()) / getWidth());
         int value = mOriginalValue + delta;
         if (value < -100) {
             value = -100;
@@ -170,4 +167,8 @@
         return true;
     }
 
+    public void reset() {
+        mOriginalValue = 0;
+    }
+
 }
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
index f2b576f..5c246e8 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.ui;
 
+import com.android.gallery3d.R;
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumSetDataLoader;
 import com.android.gallery3d.data.MediaObject;
@@ -29,6 +30,7 @@
     private final int mPlaceholderColor;
 
     private final ColorTexture mWaitLoadingTexture;
+    private final ResourceTexture mCameraOverlay;
     private final AbstractGalleryActivity mActivity;
     private final SelectionManager mSelectionManager;
     protected final LabelSpec mLabelSpec;
@@ -68,6 +70,8 @@
 
         mWaitLoadingTexture = new ColorTexture(mPlaceholderColor);
         mWaitLoadingTexture.setSize(1, 1);
+        mCameraOverlay = new ResourceTexture(activity,
+                R.drawable.frame_overlay_gallery_camera);
     }
 
     public void setPressedIndex(int index) {
@@ -122,6 +126,12 @@
     protected int renderOverlay(
             GLCanvas canvas, int index, AlbumSetEntry entry, int width, int height) {
         int renderRequestFlags = 0;
+        if (entry.album != null && entry.album.isCameraRoll()) {
+            int minDim = Math.min(width, height);
+            int dim = minDim / 2;
+            int pos = (minDim - dim) / 2;
+            mCameraOverlay.draw(canvas, pos, pos, dim, dim);
+        }
         if (mPressedIndex == index) {
             if (mAnimatePressedUp) {
                 drawPressedUpFrame(canvas, width, height);
diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java
index 99cf057..7b8e30d 100644
--- a/src/com/android/gallery3d/ui/BasicTexture.java
+++ b/src/com/android/gallery3d/ui/BasicTexture.java
@@ -42,8 +42,8 @@
     protected int mWidth = UNSPECIFIED;
     protected int mHeight = UNSPECIFIED;
 
-    protected int mTextureWidth;
-    protected int mTextureHeight;
+    private int mTextureWidth;
+    private int mTextureHeight;
 
     private boolean mHasBorder;
 
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
index 25f88a1..bf31bcb 100644
--- a/src/com/android/gallery3d/ui/BitmapScreenNail.java
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -37,7 +37,7 @@
     // The duration of the fading animation in milliseconds
     private static final int DURATION = 180;
 
-    private static final int MAX_SIDE = 640;
+    private static int sMaxSide = 640;
 
     // These are special values for mAnimationStartTime
     private static final long ANIMATION_NOT_NEEDED = -1;
@@ -46,16 +46,16 @@
 
     private int mWidth;
     private int mHeight;
-    private long mAnimationStartTime = ANIMATION_NOT_NEEDED;
-
     private Bitmap mBitmap;
-    private TiledTexture mTexture;
+    private BitmapTexture mTexture;
+    private long mAnimationStartTime = ANIMATION_NOT_NEEDED;
 
     public BitmapScreenNail(Bitmap bitmap) {
         mWidth = bitmap.getWidth();
         mHeight = bitmap.getHeight();
         mBitmap = bitmap;
-        mTexture = new TiledTexture(bitmap);
+        // We create mTexture lazily, so we don't incur the cost if we don't
+        // actually need it.
     }
 
     public BitmapScreenNail(int width, int height) {
@@ -73,10 +73,10 @@
 
     private void setSize(int width, int height) {
         if (width == 0 || height == 0) {
-            width = 640;
-            height = 480;
+            width = sMaxSide;
+            height = sMaxSide * 3 / 4;
         }
-        float scale = Math.min(1, (float) MAX_SIDE / Math.max(width, height));
+        float scale = Math.min(1, (float) sMaxSide / Math.max(width, height));
         mWidth = Math.round(scale * width);
         mHeight = Math.round(scale * height);
     }
@@ -103,14 +103,17 @@
         BitmapScreenNail newer = (BitmapScreenNail) other;
         mWidth = newer.mWidth;
         mHeight = newer.mHeight;
-        if (newer.mTexture != null) {
+        if (newer.mBitmap != null) {
             recycleBitmap(MediaItem.getThumbPool(), mBitmap);
-            if (mTexture != null) mTexture.recycle();
             mBitmap = newer.mBitmap;
-            mTexture = newer.mTexture;
             newer.mBitmap = null;
-            newer.mTexture = null;
+
+            if (mTexture != null) {
+                mTexture.recycle();
+                mTexture = null;
+            }
         }
+
         newer.recycle();
         return this;
     }
@@ -155,7 +158,7 @@
 
     @Override
     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
-        if (mTexture == null || !mTexture.isReady()) {
+        if (mBitmap == null) {
             if (mAnimationStartTime == ANIMATION_NOT_NEEDED) {
                 mAnimationStartTime = ANIMATION_NEEDED;
             }
@@ -165,12 +168,16 @@
             return;
         }
 
+        if (mTexture == null) {
+            mTexture = new BitmapTexture(mBitmap);
+        }
+
         if (mAnimationStartTime == ANIMATION_NEEDED) {
-            mAnimationStartTime = AnimationTime.get();
+            mAnimationStartTime = now();
         }
 
         if (isAnimating()) {
-            mTexture.drawMixed(canvas, mPlaceholderColor, getRatio(), x, y,
+            canvas.drawMixed(mTexture, mPlaceholderColor, getRatio(), x, y,
                     width, height);
         } else {
             mTexture.draw(canvas, x, y, width, height);
@@ -179,26 +186,34 @@
 
     @Override
     public void draw(GLCanvas canvas, RectF source, RectF dest) {
-        if (mTexture == null || !mTexture.isReady()) {
+        if (mBitmap == null) {
             canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(),
                     mPlaceholderColor);
             return;
         }
 
-        mTexture.draw(canvas, source, dest);
+        if (mTexture == null) {
+            mTexture = new BitmapTexture(mBitmap);
+        }
+
+        canvas.drawTexture(mTexture, source, dest);
     }
 
     public boolean isAnimating() {
         if (mAnimationStartTime < 0) return false;
-        if (AnimationTime.get() - mAnimationStartTime >= DURATION) {
+        if (now() - mAnimationStartTime >= DURATION) {
             mAnimationStartTime = ANIMATION_DONE;
             return false;
         }
         return true;
     }
 
+    private static long now() {
+        return AnimationTime.get();
+    }
+
     private float getRatio() {
-        float r = (float) (AnimationTime.get() - mAnimationStartTime) / DURATION;
+        float r = (float)(now() - mAnimationStartTime) / DURATION;
         return Utils.clamp(1.0f - r, 0.0f, 1.0f);
     }
 
@@ -206,7 +221,7 @@
         return (mBitmap == null) || isAnimating();
     }
 
-    public TiledTexture getTexture() {
-        return mTexture;
+    public static void setMaxSide(int size) {
+        sMaxSide = size;
     }
 }
diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java
index 6f8baef..e3a32ef 100644
--- a/src/com/android/gallery3d/ui/GLCanvas.java
+++ b/src/com/android/gallery3d/ui/GLCanvas.java
@@ -99,13 +99,6 @@
     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();
diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java
index 45903b3..d83daf3 100644
--- a/src/com/android/gallery3d/ui/GLCanvasImpl.java
+++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java
@@ -415,7 +415,7 @@
     // 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,
+    private void convertCoordinate(RectF source, RectF target,
             BasicTexture texture) {
 
         int width = texture.getWidth();
@@ -465,82 +465,6 @@
         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
@@ -558,9 +482,45 @@
         final GL11 gl = mGL;
         if (!bindTexture(from)) return;
 
+        //
+        // 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);
+
         // Interpolate the RGB and alpha values between both textures.
         mGLState.setTexEnvMode(GL11.GL_COMBINE);
-        setMixedColor(toColor, ratio, alpha);
+
+        // 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);
+        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);
 
         drawBoundTexture(from, x, y, width, height);
         mGLState.setTexEnvMode(GL11.GL_REPLACE);
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index a7357e0..3fa13cf 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -174,8 +174,8 @@
     public static final int SCREEN_NAIL_MAX = 3;
 
     // These are constants for the delete gesture.
-    private static final int SWIPE_ESCAPE_VELOCITY = 500; // dp/sec
-    private static final int MAX_DISMISS_VELOCITY = 2000; // dp/sec
+    private static final int SWIPE_ESCAPE_VELOCITY = 2500; // dp/sec
+    private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
 
     // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
     // SCREEN_NAIL_MAX.
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 45b5ec6..fffa7e0 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -319,10 +319,10 @@
         b.mImageW = width;
         b.mImageH = height;
 
-        // If this is the first time we receive an image size, we change the
-        // scale directly. Otherwise adjust the scales by a ratio, and snapback
-        // will animate the scale into the min/max bounds if necessary.
-        if (wasViewSize && !isViewSize) {
+        // If this is the first time we receive an image size or we are in fullscreen,
+        // we change the scale directly. Otherwise adjust the scales by a ratio,
+        // and snapback will animate the scale into the min/max bounds if necessary.
+        if ((wasViewSize && !isViewSize) || !mFilmMode) {
             b.mCurrentScale = getMinimalScale(b);
             b.mAnimationStartTime = NO_ANIMATION;
         } else {
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 18a7af8..5ce06be 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -575,10 +575,8 @@
                 }
                 if (tile == null) break;
                 if (!tile.isContentValid()) {
-                    boolean hasBeenLoaded = tile.isLoaded();
                     Utils.assertTrue(tile.mTileState == STATE_DECODED);
                     tile.updateContent(canvas);
-                    if (!hasBeenLoaded) tile.draw(canvas, 0, 0);
                     --quota;
                 }
             }
@@ -623,6 +621,7 @@
         }
     }
 
+    // TODO: avoid drawing the unused part of the textures.
     static boolean drawTile(
             Tile tile, GLCanvas canvas, RectF source, RectF target) {
         while (true) {
diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/ui/TiledTexture.java
deleted file mode 100644
index 6e9ad9e..0000000
--- a/src/com/android/gallery3d/ui/TiledTexture.java
+++ /dev/null
@@ -1,298 +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 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 {
-    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;
-
-    private static Tile sFreeTileHead = null;
-    private static final Object sFreeTileLock = new Object();
-
-    private static Bitmap sUploadBitmap;
-    private static Canvas sCanvas;
-    private static Paint sPaint;
-
-    private int mUploadIndex = 0;
-
-    private final Tile[] mTiles;
-    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) {
-                if (!deque.isEmpty()) {
-                    TiledTexture t = deque.peekFirst();
-                    if (t.uploadNextTile(canvas)) {
-                        deque.removeFirst();
-                        mGlRoot.requestRender();
-                    }
-                }
-                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, null);
-            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;
-        Tile next = mTiles[mUploadIndex++];
-        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;
-    }
-
-    public void recycle() {
-        for (int i = 0, n = mTiles.length; i < n; ++i) {
-            freeTile(mTiles[i]);
-        }
-    }
-
-    public static void freeResources() {
-        sUploadBitmap = null;
-        sCanvas = null;
-        sPaint = null;
-    }
-
-    public static void prepareResources() {
-        sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
-        sCanvas = new Canvas(sUploadBitmap);
-        sPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-        sPaint.setColor(Color.TRANSPARENT);
-        sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
-    }
-
-    // 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;
-        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.
-    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;
-        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();
-
-        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);
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index efa0614..05bd3de 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -75,16 +75,25 @@
     private static boolean sCameraAvailable;
 
     public static void initialize(Context context) {
-        if (sPixelDensity < 0) {
-            DisplayMetrics metrics = new DisplayMetrics();
-            WindowManager wm = (WindowManager)
-                    context.getSystemService(Context.WINDOW_SERVICE);
-            wm.getDefaultDisplay().getMetrics(metrics);
-            sPixelDensity = metrics.density;
-        }
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        sPixelDensity = metrics.density;
         Resources r = context.getResources();
         BitmapScreenNail.setPlaceholderColor(r.getColor(
                 R.color.bitmap_screennail_placeholder));
+        initializeThumbnailSizes(metrics, r);
+    }
+
+    private static void initializeThumbnailSizes(DisplayMetrics metrics, Resources r) {
+        int minRows = Math.min(r.getInteger(R.integer.album_rows_land),
+                r.getInteger(R.integer.albumset_rows_land));
+        int maxDimensionPixels = Math.max(metrics.heightPixels, metrics.widthPixels);
+        // Never need to completely fill the screen
+        maxDimensionPixels = maxDimensionPixels * 3/4;
+        MediaItem.setThumbnailSizes(maxDimensionPixels, maxDimensionPixels / minRows);
+        BitmapScreenNail.setMaxSide(maxDimensionPixels);
     }
 
     public static boolean isHighResolution(Context context) {
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
index 5a08b85..2f2d753 100644
--- a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
+++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
@@ -83,6 +83,4 @@
     public void dumpStatisticsAndClear() {}
     public void beginRenderTarget(RawTexture texture) {}
     public void endRenderTarget() {}
-    public void drawMixed(BasicTexture from, int toColor,
-            float ratio, RectF src, RectF target) {}
 }