Merge "Release surface texture to fix gray camera preview" into gb-ub-photos-bryce
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 508aefe..3cd30cb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -88,8 +88,11 @@
              </intent-filter>
         </activity>
 
-        <activity android:name="com.android.gallery3d.app.Gallery" android:label="@string/app_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
+        <activity android:name="com.android.photos.GalleryActivity"
+                android:label="@string/app_name"
+                android:configChanges="keyboardHidden|orientation|screenSize"
+                android:theme="@style/Theme.Photos.Gallery"
+                android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -164,17 +167,6 @@
             </intent-filter>
         </activity>
 
-        <!-- we add this activity-alias for shortcut backward compatibility -->
-        <!-- Note: The alias must put after the target activity -->
-        <activity-alias android:name="com.cooliris.media.Gallery"
-                android:targetActivity="com.android.gallery3d.app.Gallery"
-                android:configChanges="keyboardHidden|orientation|screenSize"
-                android:label="@string/app_name">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-            </intent-filter>
-        </activity-alias>
-
          <!-- This activity receives USB_DEVICE_ATTACHED intents and allows importing
          media from attached MTP devices, like cameras and camera phones -->
         <activity android:launchMode="singleInstance"
diff --git a/jni/Android.mk b/jni/Android.mk
index 1843c77..e612486 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -32,6 +32,7 @@
                    filters/contrast.c \
                    filters/hue.c \
                    filters/shadows.c \
+                   filters/highlight.c \
                    filters/hsv.c \
                    filters/vibrance.c \
                    filters/geometry.c \
diff --git a/jni/filters/contrast.c b/jni/filters/contrast.c
index 6c1b976..b04e936 100644
--- a/jni/filters/contrast.c
+++ b/jni/filters/contrast.c
@@ -27,6 +27,15 @@
     return  (unsigned char) c;
 }
 
+int clampMax(int c,int max)
+{
+    c &= ~(c >> 31);
+    c -= max;
+    c &= (c >> 31);
+    c += max;
+    return  c;
+}
+
 void JNIFUNCF(ImageFilterContrast, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat bright)
 {
     char* destination = 0;
diff --git a/jni/filters/filters.h b/jni/filters/filters.h
index d518b63..14b69cd 100644
--- a/jni/filters/filters.h
+++ b/jni/filters/filters.h
@@ -44,6 +44,7 @@
 #define CLAMP(c) (MAX(0, MIN(255, c)))
 
 __inline__ unsigned char  clamp(int c);
+__inline__ int clampMax(int c,int max);
 
 extern void rgb2hsv( unsigned char *rgb,int rgbOff,unsigned short *hsv,int hsvOff);
 extern void hsv2rgb(unsigned short *hsv,int hsvOff,unsigned char  *rgb,int rgbOff);
diff --git a/jni/filters/highlight.c b/jni/filters/highlight.c
new file mode 100644
index 0000000..fe9b88f
--- /dev/null
+++ b/jni/filters/highlight.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math.h>
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterHighlights, nativeApplyFilter, jobject bitmap,
+              jint width, jint height, jfloatArray luminanceMap){
+    char* destination = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+    unsigned char * rgb = (unsigned char * )destination;
+    int i;
+    int len = width * height * 4;
+    jfloat* lum = (*env)->GetFloatArrayElements(env, luminanceMap,0);
+    unsigned short * hsv = (unsigned short *)malloc(3*sizeof(short));
+
+    for (i = 0; i < len; i+=4)
+    {
+        rgb2hsv(rgb,i,hsv,0);
+        int v = clampMax(hsv[0],4080);
+        hsv[0] = (unsigned short) clampMax(lum[((255*v)/4080)]*4080,4080);
+        hsv2rgb(hsv,0, rgb,i);
+    }
+
+    free(hsv);
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/res/layout/photo_set.xml b/res/layout/photo_set.xml
new file mode 100644
index 0000000..2bb97bb
--- /dev/null
+++ b/res/layout/photo_set.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="8dp"
+    android:paddingRight="8dp" >
+
+    <ListView
+        android:id="@id/android:list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:drawSelectorOnTop="true" />
+
+    <TextView
+        android:id="@id/android:empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/empty_album" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/menu/gallery.xml b/res/menu/gallery.xml
new file mode 100644
index 0000000..dc36787
--- /dev/null
+++ b/res/menu/gallery.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/menu_camera"
+        android:icon="@android:drawable/ic_menu_camera"
+        android:showAsAction="ifRoom"
+        android:title="@string/menu_camera"/>
+    <item
+        android:id="@+id/menu_search"
+        android:icon="@android:drawable/ic_menu_search"
+        android:showAsAction="ifRoom"
+        android:title="@string/menu_search"/>
+    <item
+        android:id="@+id/menu_settings"
+        android:icon="@android:drawable/ic_menu_preferences"
+        android:showAsAction="never"
+        android:title="@string/settings"/>
+    <item
+        android:id="@+id/menu_help"
+        android:icon="@android:drawable/ic_menu_help"
+        android:showAsAction="never"
+        android:title="@string/help"/>
+</menu>
\ No newline at end of file
diff --git a/res/values-af/filtershow_strings.xml b/res/values-af/filtershow_strings.xml
index 9f5751e..34e5ebf 100644
--- a/res/values-af/filtershow_strings.xml
+++ b/res/values-af/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Outokleur"</string>
     <string name="hue" msgid="6231252147971086030">"Kleur"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skaduwees"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurwes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjet"</string>
     <string name="redeye" msgid="4508883127049472069">"Rooi oog"</string>
diff --git a/res/values-am/filtershow_strings.xml b/res/values-am/filtershow_strings.xml
index cf72a53..eb0549d 100644
--- a/res/values-am/filtershow_strings.xml
+++ b/res/values-am/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"ራስ-ቀለም መሙላት"</string>
     <string name="hue" msgid="6231252147971086030">"የቀለም ድባብ"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"ጥላዎች"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"ጥምዞች"</string>
     <string name="vignette" msgid="934721068851885390">"ቪኜት"</string>
     <string name="redeye" msgid="4508883127049472069">"ቀይ አይን"</string>
diff --git a/res/values-ar/filtershow_strings.xml b/res/values-ar/filtershow_strings.xml
index ee1bfca..0310852 100644
--- a/res/values-ar/filtershow_strings.xml
+++ b/res/values-ar/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"لون تلقائي"</string>
     <string name="hue" msgid="6231252147971086030">"تدرج اللون"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"ظلال"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"المنحنيات"</string>
     <string name="vignette" msgid="934721068851885390">"نقوش صورة نصفية"</string>
     <string name="redeye" msgid="4508883127049472069">"العين الحمراء"</string>
diff --git a/res/values-be/filtershow_strings.xml b/res/values-be/filtershow_strings.xml
index 06af904..73d96e0 100644
--- a/res/values-be/filtershow_strings.xml
+++ b/res/values-be/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Aўтаколер"</string>
     <string name="hue" msgid="6231252147971086030">"Тон"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Цені"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Пэндзаль"</string>
     <string name="vignette" msgid="934721068851885390">"Віньетка"</string>
     <string name="redeye" msgid="4508883127049472069">"Чырвонае вока"</string>
diff --git a/res/values-bg/filtershow_strings.xml b/res/values-bg/filtershow_strings.xml
index cc4daae..f6e9cdc 100644
--- a/res/values-bg/filtershow_strings.xml
+++ b/res/values-bg/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Авт. цвят"</string>
     <string name="hue" msgid="6231252147971086030">"Нюанс"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Засенчване"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Извивки"</string>
     <string name="vignette" msgid="934721068851885390">"Винетиране"</string>
     <string name="redeye" msgid="4508883127049472069">"Червени очи"</string>
diff --git a/res/values-ca/filtershow_strings.xml b/res/values-ca/filtershow_strings.xml
index 5f4b8e1..6c291e8 100644
--- a/res/values-ca/filtershow_strings.xml
+++ b/res/values-ca/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"To de color"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Corbes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ulls vermells"</string>
diff --git a/res/values-cs/filtershow_strings.xml b/res/values-cs/filtershow_strings.xml
index e0c59f5..9b1db5f 100644
--- a/res/values-cs/filtershow_strings.xml
+++ b/res/values-cs/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. barva"</string>
     <string name="hue" msgid="6231252147971086030">"Odstín"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Stíny"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Křivky"</string>
     <string name="vignette" msgid="934721068851885390">"Viněta"</string>
     <string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
diff --git a/res/values-da/filtershow_strings.xml b/res/values-da/filtershow_strings.xml
index bc5ed33..58fa4fd 100644
--- a/res/values-da/filtershow_strings.xml
+++ b/res/values-da/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Automatisk farve"</string>
     <string name="hue" msgid="6231252147971086030">"Nuance"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
     <string name="vignette" msgid="934721068851885390">"Vignet"</string>
     <string name="redeye" msgid="4508883127049472069">"Røde øjne"</string>
diff --git a/res/values-de/filtershow_strings.xml b/res/values-de/filtershow_strings.xml
index 0c660bc..c6ce09d 100644
--- a/res/values-de/filtershow_strings.xml
+++ b/res/values-de/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. Farbe"</string>
     <string name="hue" msgid="6231252147971086030">"Farbton"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Schatten"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurven"</string>
     <string name="vignette" msgid="934721068851885390">"Vignettierung"</string>
     <string name="redeye" msgid="4508883127049472069">"Rote Augen"</string>
diff --git a/res/values-el/filtershow_strings.xml b/res/values-el/filtershow_strings.xml
index 40acfdc..0f23e0f 100644
--- a/res/values-el/filtershow_strings.xml
+++ b/res/values-el/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Αυτόματο χρώμα"</string>
     <string name="hue" msgid="6231252147971086030">"Απόχρωση"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Σκιές"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Καμπύλες"</string>
     <string name="vignette" msgid="934721068851885390">"Βινιετάρισμα"</string>
     <string name="redeye" msgid="4508883127049472069">"Κόκκινα μάτια"</string>
diff --git a/res/values-en-rGB/filtershow_strings.xml b/res/values-en-rGB/filtershow_strings.xml
index ad6e4e0..a1fba2f 100644
--- a/res/values-en-rGB/filtershow_strings.xml
+++ b/res/values-en-rGB/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolour"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Shadows"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curves"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
diff --git a/res/values-es-rUS/filtershow_strings.xml b/res/values-es-rUS/filtershow_strings.xml
index e3a9a8e..96c7678 100644
--- a/res/values-es-rUS/filtershow_strings.xml
+++ b/res/values-es-rUS/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Color autom."</string>
     <string name="hue" msgid="6231252147971086030">"Tono"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Viñeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
diff --git a/res/values-es/filtershow_strings.xml b/res/values-es/filtershow_strings.xml
index e3820fa..0f0d69e 100644
--- a/res/values-es/filtershow_strings.xml
+++ b/res/values-es/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Color automático"</string>
     <string name="hue" msgid="6231252147971086030">"Tonalidad"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curvar"</string>
     <string name="vignette" msgid="934721068851885390">"Viñeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
diff --git a/res/values-et/filtershow_strings.xml b/res/values-et/filtershow_strings.xml
index d7be6e5..f741463 100644
--- a/res/values-et/filtershow_strings.xml
+++ b/res/values-et/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. värvid"</string>
     <string name="hue" msgid="6231252147971086030">"Värvitoon"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Varjud"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kõverad"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjett"</string>
     <string name="redeye" msgid="4508883127049472069">"Punasilmsus"</string>
diff --git a/res/values-fa/filtershow_strings.xml b/res/values-fa/filtershow_strings.xml
index 3051612..30fb365 100644
--- a/res/values-fa/filtershow_strings.xml
+++ b/res/values-fa/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"رنگ خودکار"</string>
     <string name="hue" msgid="6231252147971086030">"رنگ‌مایه"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"سایه‌ها"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"نمودارها"</string>
     <string name="vignette" msgid="934721068851885390">"محو لبه‌ها"</string>
     <string name="redeye" msgid="4508883127049472069">"قرمزی چشم"</string>
diff --git a/res/values-fi/filtershow_strings.xml b/res/values-fi/filtershow_strings.xml
index 1c9491c..44ca6c3 100644
--- a/res/values-fi/filtershow_strings.xml
+++ b/res/values-fi/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. värit"</string>
     <string name="hue" msgid="6231252147971086030">"Sävy"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Tummat alueet"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Valotuskäyrät"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjetti"</string>
     <string name="redeye" msgid="4508883127049472069">"Punasilmäisyys"</string>
diff --git a/res/values-fr/filtershow_strings.xml b/res/values-fr/filtershow_strings.xml
index 868e61a..b6ffb6e 100644
--- a/res/values-fr/filtershow_strings.xml
+++ b/res/values-fr/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Coloration auto"</string>
     <string name="hue" msgid="6231252147971086030">"Teinte"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Courbes"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetage"</string>
     <string name="redeye" msgid="4508883127049472069">"Yeux rouges"</string>
diff --git a/res/values-hi/filtershow_strings.xml b/res/values-hi/filtershow_strings.xml
index 88ac1a4..30d47c8 100644
--- a/res/values-hi/filtershow_strings.xml
+++ b/res/values-hi/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"ऑटोकलर"</string>
     <string name="hue" msgid="6231252147971086030">"ह्यू"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"छाया"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"वक्र"</string>
     <string name="vignette" msgid="934721068851885390">"विनेट"</string>
     <string name="redeye" msgid="4508883127049472069">"रेड आई"</string>
diff --git a/res/values-hr/filtershow_strings.xml b/res/values-hr/filtershow_strings.xml
index ee4b51d..99a3721 100644
--- a/res/values-hr/filtershow_strings.xml
+++ b/res/values-hr/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Automatska boja"</string>
     <string name="hue" msgid="6231252147971086030">"Nijansa"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sjenke"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Crvene oči"</string>
diff --git a/res/values-hu/filtershow_strings.xml b/res/values-hu/filtershow_strings.xml
index cb0a78e..f577e0b 100644
--- a/res/values-hu/filtershow_strings.xml
+++ b/res/values-hu/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Automat. szín"</string>
     <string name="hue" msgid="6231252147971086030">"Színárnyalat"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Árnyékok"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Görbék"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetta"</string>
     <string name="redeye" msgid="4508883127049472069">"Vörösszem"</string>
diff --git a/res/values-in/filtershow_strings.xml b/res/values-in/filtershow_strings.xml
index eb30676..39f407f 100644
--- a/res/values-in/filtershow_strings.xml
+++ b/res/values-in/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Warna Otomatis"</string>
     <string name="hue" msgid="6231252147971086030">"Rona"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bayangan"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurva"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyet"</string>
     <string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
diff --git a/res/values-it/filtershow_strings.xml b/res/values-it/filtershow_strings.xml
index 8de522c..ac4294a 100644
--- a/res/values-it/filtershow_strings.xml
+++ b/res/values-it/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Colore autom."</string>
     <string name="hue" msgid="6231252147971086030">"Tonalità"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombre"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curve"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetta"</string>
     <string name="redeye" msgid="4508883127049472069">"Occhi rossi"</string>
diff --git a/res/values-iw/filtershow_strings.xml b/res/values-iw/filtershow_strings.xml
index 36f7a51..c740611 100644
--- a/res/values-iw/filtershow_strings.xml
+++ b/res/values-iw/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"צבע אוטומטי"</string>
     <string name="hue" msgid="6231252147971086030">"גוון"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"צלליות"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"קימורים"</string>
     <string name="vignette" msgid="934721068851885390">"עמעום קצוות"</string>
     <string name="redeye" msgid="4508883127049472069">"עיניים אדומות"</string>
diff --git a/res/values-ja/filtershow_strings.xml b/res/values-ja/filtershow_strings.xml
index 8ad69e1..289faef 100644
--- a/res/values-ja/filtershow_strings.xml
+++ b/res/values-ja/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"自動色補正"</string>
     <string name="hue" msgid="6231252147971086030">"色彩"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"影"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"カーブ"</string>
     <string name="vignette" msgid="934721068851885390">"周辺減光"</string>
     <string name="redeye" msgid="4508883127049472069">"赤目処理"</string>
diff --git a/res/values-ko/filtershow_strings.xml b/res/values-ko/filtershow_strings.xml
index 9a7005b..b2eb59a 100644
--- a/res/values-ko/filtershow_strings.xml
+++ b/res/values-ko/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"자동 색상"</string>
     <string name="hue" msgid="6231252147971086030">"색조"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"그림자"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"곡선"</string>
     <string name="vignette" msgid="934721068851885390">"비네트"</string>
     <string name="redeye" msgid="4508883127049472069">"적목현상 없애기"</string>
diff --git a/res/values-lt/filtershow_strings.xml b/res/values-lt/filtershow_strings.xml
index 06e6d28..819e668 100644
--- a/res/values-lt/filtershow_strings.xml
+++ b/res/values-lt/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. spalva"</string>
     <string name="hue" msgid="6231252147971086030">"Atspalvis"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Šešėliai"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kreivės"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjetė"</string>
     <string name="redeye" msgid="4508883127049472069">"Raudonos akys"</string>
diff --git a/res/values-lv/filtershow_strings.xml b/res/values-lv/filtershow_strings.xml
index 9ac6f7f..29e1afd 100644
--- a/res/values-lv/filtershow_strings.xml
+++ b/res/values-lv/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Aut. krāsu pal."</string>
     <string name="hue" msgid="6231252147971086030">"Nokrāsa"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ēnas"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Līknes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjete"</string>
     <string name="redeye" msgid="4508883127049472069">"Sarkano acu ef."</string>
diff --git a/res/values-ms/filtershow_strings.xml b/res/values-ms/filtershow_strings.xml
index ee7287c..d175a44 100644
--- a/res/values-ms/filtershow_strings.xml
+++ b/res/values-ms/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autowarna"</string>
     <string name="hue" msgid="6231252147971086030">"Rona"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bayang-bayang"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Lengkung"</string>
     <string name="vignette" msgid="934721068851885390">"Vignet"</string>
     <string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
diff --git a/res/values-nb/filtershow_strings.xml b/res/values-nb/filtershow_strings.xml
index 3f0f06a..84cee67 100644
--- a/res/values-nb/filtershow_strings.xml
+++ b/res/values-nb/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autofarger"</string>
     <string name="hue" msgid="6231252147971086030">"Nyanse"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
     <string name="vignette" msgid="934721068851885390">"Vignettering"</string>
     <string name="redeye" msgid="4508883127049472069">"Røde øyne"</string>
diff --git a/res/values-nl/filtershow_strings.xml b/res/values-nl/filtershow_strings.xml
index 8bbbe3b..1876a4a 100644
--- a/res/values-nl/filtershow_strings.xml
+++ b/res/values-nl/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Auto-kleur"</string>
     <string name="hue" msgid="6231252147971086030">"Kleurschakering"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Schaduw"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curven"</string>
     <string name="vignette" msgid="934721068851885390">"Vervloeien"</string>
     <string name="redeye" msgid="4508883127049472069">"Rode ogen"</string>
diff --git a/res/values-pl/filtershow_strings.xml b/res/values-pl/filtershow_strings.xml
index 48a45cc..a658e50 100644
--- a/res/values-pl/filtershow_strings.xml
+++ b/res/values-pl/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autokolor"</string>
     <string name="hue" msgid="6231252147971086030">"Odcień"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Cienie"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Krzywe"</string>
     <string name="vignette" msgid="934721068851885390">"Winietowanie"</string>
     <string name="redeye" msgid="4508883127049472069">"Czerwone oczy"</string>
diff --git a/res/values-pt-rPT/filtershow_strings.xml b/res/values-pt-rPT/filtershow_strings.xml
index e94bf45..9dd5bb8 100644
--- a/res/values-pt-rPT/filtershow_strings.xml
+++ b/res/values-pt-rPT/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
     <string name="hue" msgid="6231252147971086030">"Tonalidade"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Vinheta"</string>
     <string name="redeye" msgid="4508883127049472069">"Olhos Vermelhos"</string>
diff --git a/res/values-pt/filtershow_strings.xml b/res/values-pt/filtershow_strings.xml
index 8c80270..3ab7d02 100644
--- a/res/values-pt/filtershow_strings.xml
+++ b/res/values-pt/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
     <string name="hue" msgid="6231252147971086030">"Matiz"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Vinheta"</string>
     <string name="redeye" msgid="4508883127049472069">"Olhos vermelhos"</string>
diff --git a/res/values-ro/filtershow_strings.xml b/res/values-ro/filtershow_strings.xml
index 46be094..688965b 100644
--- a/res/values-ro/filtershow_strings.xml
+++ b/res/values-ro/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Culoare auto."</string>
     <string name="hue" msgid="6231252147971086030">"Tonalitate"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Umbre"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Curbe"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetare"</string>
     <string name="redeye" msgid="4508883127049472069">"Ochi roşii"</string>
diff --git a/res/values-ru/filtershow_strings.xml b/res/values-ru/filtershow_strings.xml
index 42d6db5..cb69521 100644
--- a/res/values-ru/filtershow_strings.xml
+++ b/res/values-ru/filtershow_strings.xml
@@ -45,7 +45,7 @@
     <string name="aspect5to7_effect" msgid="5122395569059384741">"5:7"</string>
     <string name="aspect7to5_effect" msgid="5780001758108328143">"7:5"</string>
     <string name="aspect9to16_effect" msgid="7740468012919660728">"16:9"</string>
-    <string name="aspectNone_effect" msgid="6263330561046574134">"Оригинал"</string>
+    <string name="aspectNone_effect" msgid="6263330561046574134">"Вручную"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
     <string name="Fixed" msgid="8017376448916924565">"Постоянное"</string>
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Авторежим"</string>
     <string name="hue" msgid="6231252147971086030">"Оттенок"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Тени"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Кривые"</string>
     <string name="vignette" msgid="934721068851885390">"Виньет-ние"</string>
     <string name="redeye" msgid="4508883127049472069">"Красные глаза"</string>
diff --git a/res/values-sk/filtershow_strings.xml b/res/values-sk/filtershow_strings.xml
index a49bd3c..3d57b44 100644
--- a/res/values-sk/filtershow_strings.xml
+++ b/res/values-sk/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. farba"</string>
     <string name="hue" msgid="6231252147971086030">"Odtieň"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Tiene"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Krivky"</string>
     <string name="vignette" msgid="934721068851885390">"Vineta"</string>
     <string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
diff --git a/res/values-sl/filtershow_strings.xml b/res/values-sl/filtershow_strings.xml
index e0539e3..dbf2e4b 100644
--- a/res/values-sl/filtershow_strings.xml
+++ b/res/values-sl/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Samodej. barva"</string>
     <string name="hue" msgid="6231252147971086030">"Odtenek"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sence"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Rdeče oči"</string>
diff --git a/res/values-sr/filtershow_strings.xml b/res/values-sr/filtershow_strings.xml
index d5837d9..af002bf 100644
--- a/res/values-sr/filtershow_strings.xml
+++ b/res/values-sr/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Аутоматска боја"</string>
     <string name="hue" msgid="6231252147971086030">"Нијанса"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Сенке"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Криве"</string>
     <string name="vignette" msgid="934721068851885390">"Вињета"</string>
     <string name="redeye" msgid="4508883127049472069">"Црвене очи"</string>
diff --git a/res/values-sv/filtershow_strings.xml b/res/values-sv/filtershow_strings.xml
index f43c7d2..12057c3 100644
--- a/res/values-sv/filtershow_strings.xml
+++ b/res/values-sv/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autofärg"</string>
     <string name="hue" msgid="6231252147971086030">"Nyans"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skuggor"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Kurvor"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Röda ögon"</string>
diff --git a/res/values-sw/filtershow_strings.xml b/res/values-sw/filtershow_strings.xml
index 9f26a05..9b5f243 100644
--- a/res/values-sw/filtershow_strings.xml
+++ b/res/values-sw/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Rangi otomatiki"</string>
     <string name="hue" msgid="6231252147971086030">"Rangi"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Vivuli"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Pindo"</string>
     <string name="vignette" msgid="934721068851885390">"Vignete"</string>
     <string name="redeye" msgid="4508883127049472069">"Jicho Jekundu"</string>
diff --git a/res/values-th/filtershow_strings.xml b/res/values-th/filtershow_strings.xml
index 1f86b14..f508315 100644
--- a/res/values-th/filtershow_strings.xml
+++ b/res/values-th/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"ให้สีอัตโนมัติ"</string>
     <string name="hue" msgid="6231252147971086030">"สี"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"เงา"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"เส้นโค้ง"</string>
     <string name="vignette" msgid="934721068851885390">"วิกเน็ตต์"</string>
     <string name="redeye" msgid="4508883127049472069">"ตาแดง"</string>
diff --git a/res/values-tl/filtershow_strings.xml b/res/values-tl/filtershow_strings.xml
index 8ea767a..fee5bfe 100644
--- a/res/values-tl/filtershow_strings.xml
+++ b/res/values-tl/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Mga Shadow"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Mga Kurba"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
diff --git a/res/values-tr/filtershow_strings.xml b/res/values-tr/filtershow_strings.xml
index cdc8189..d9808ae 100644
--- a/res/values-tr/filtershow_strings.xml
+++ b/res/values-tr/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Otomatik Renk"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Gölgeler"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Eğriler"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyet"</string>
     <string name="redeye" msgid="4508883127049472069">"Kırmızı Göz"</string>
diff --git a/res/values-uk/filtershow_strings.xml b/res/values-uk/filtershow_strings.xml
index a05ef93..e2f4d92 100644
--- a/res/values-uk/filtershow_strings.xml
+++ b/res/values-uk/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Автоколір"</string>
     <string name="hue" msgid="6231252147971086030">"Тон"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Тіні"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Криві"</string>
     <string name="vignette" msgid="934721068851885390">"Віньєтка"</string>
     <string name="redeye" msgid="4508883127049472069">"Червоні очі"</string>
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
index 18f3440..18610cb 100644
--- a/res/values-v14/styles.xml
+++ b/res/values-v14/styles.xml
@@ -22,4 +22,6 @@
     <style name="ActionBarTwoLineItem">
         <item name="android:background">?android:attr/activatedBackgroundIndicator</item>
     </style>
+    <style name="Theme.Photos.Gallery" parent="android:Theme.Holo.Light">
+    </style>
 </resources>
diff --git a/res/values-vi/filtershow_strings.xml b/res/values-vi/filtershow_strings.xml
index 0f4c9d3..7e2d56b 100644
--- a/res/values-vi/filtershow_strings.xml
+++ b/res/values-vi/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"Màu tự động"</string>
     <string name="hue" msgid="6231252147971086030">"Màu sắc"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bóng"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Đồ thị màu"</string>
     <string name="vignette" msgid="934721068851885390">"Làm mờ nét ảnh"</string>
     <string name="redeye" msgid="4508883127049472069">"Mắt đỏ"</string>
diff --git a/res/values-zh-rCN/filtershow_strings.xml b/res/values-zh-rCN/filtershow_strings.xml
index 8876ce8..958f009 100644
--- a/res/values-zh-rCN/filtershow_strings.xml
+++ b/res/values-zh-rCN/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"自动调整色彩"</string>
     <string name="hue" msgid="6231252147971086030">"色调"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"阴影"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"曲线"</string>
     <string name="vignette" msgid="934721068851885390">"晕影"</string>
     <string name="redeye" msgid="4508883127049472069">"红眼"</string>
diff --git a/res/values-zh-rTW/filtershow_strings.xml b/res/values-zh-rTW/filtershow_strings.xml
index 8f205fb..eb4526c 100644
--- a/res/values-zh-rTW/filtershow_strings.xml
+++ b/res/values-zh-rTW/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"自動色彩校正"</string>
     <string name="hue" msgid="6231252147971086030">"色調"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"陰影"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"曲線"</string>
     <string name="vignette" msgid="934721068851885390">"暈影"</string>
     <string name="redeye" msgid="4508883127049472069">"紅眼"</string>
diff --git a/res/values-zu/filtershow_strings.xml b/res/values-zu/filtershow_strings.xml
index 5798c10..5439804 100644
--- a/res/values-zu/filtershow_strings.xml
+++ b/res/values-zu/filtershow_strings.xml
@@ -59,6 +59,8 @@
     <string name="wbalance" msgid="6346581563387083613">"I-Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"I-Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Izithunzi"</string>
+    <!-- no translation found for highlight_recovery (8262208470735204243) -->
+    <skip />
     <string name="curvesRGB" msgid="915010781090477550">"Ukugobeka"</string>
     <string name="vignette" msgid="934721068851885390">"I-Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Iso elibomvu"</string>
diff --git a/res/values/filtershow_ids.xml b/res/values/filtershow_ids.xml
index ba31834..28e7816 100644
--- a/res/values/filtershow_ids.xml
+++ b/res/values/filtershow_ids.xml
@@ -28,6 +28,7 @@
     <item type="id" name="hueButton" />
     <item type="id" name="exposureButton" />
     <item type="id" name="shadowRecoveryButton" />
+    <item type="id" name="highlightRecoveryButton" />
     <item type="id" name="sharpenButton" />
     <item type="id" name="curvesButtonRGB" />
     <item type="id" name="negativeButton" />
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
index 66fb390..3e9e355 100644
--- a/res/values/filtershow_strings.xml
+++ b/res/values/filtershow_strings.xml
@@ -23,6 +23,8 @@
     <string name="cannot_load_image">Cannot load the image!</string>
     <!--  String displayed when showing the original image [CHAR LIMIT=NONE] -->
     <string name="original_picture_text">@string/original</string>
+    <!--  String displayed when setting the homepage wallpaper in the background [CHAR LIMIT=NONE] -->
+    <string name="setting_wallpaper">Setting wallpaper</string>
 
     <!--  generic strings -->
 
@@ -115,6 +117,8 @@
     <string name="hue">Hue</string>
     <!--  Label for the image shadow recovery (lightens/darkens shadows) filter button [CHAR LIMIT=10] -->
     <string name="shadow_recovery">Shadows</string>
+    <!--  Label for the image highlights recovery (lightens/darkens bright regions) filter button [CHAR LIMIT=15] -->
+    <string name="highlight_recovery">Highlights</string>
     <!--  Label for the image curves filter button [CHAR LIMIT=10] -->
     <string name="curvesRGB">Curves</string>
     <!--  Label for the image vignette filter (darkens photo around edges) button [CHAR LIMIT=10] -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fae3466..263b8b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1002,4 +1002,12 @@
     <!-- Positive answer for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = 20] -->
     <string name="remember_location_yes">Yes</string>
 
+    <!-- Menu item to launch the camera app [CHAR LIMIT=25] -->
+    <string name="menu_camera">Camera</string>
+    <!-- Menu item to search for photos [CHAR LIMIT=25] -->
+    <string name="menu_search">Search</string>
+    <!-- Title for the all photos tab [CHAR LIMIT=25] -->
+    <string name="tab_photos">Photos</string>
+    <!-- Title for the albums tab [CHAR LIMIT=25] -->
+    <string name="tab_albums">Albums</string>
 </resources>
diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644
index 0000000..40bab1e
--- /dev/null
+++ b/src/android/util/Pools.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/CaptureAnimManager.java b/src/com/android/camera/CaptureAnimManager.java
index 4643c37..ec38290 100644
--- a/src/com/android/camera/CaptureAnimManager.java
+++ b/src/com/android/camera/CaptureAnimManager.java
@@ -33,9 +33,9 @@
     // times mark endpoint of animation phase
     private static final int TIME_FLASH = 200;
     private static final int TIME_HOLD = 400;
-    private static final int TIME_SLIDE = 700;
-    private static final int TIME_HOLD2 = 1000;
-    private static final int TIME_SLIDE2 = 1200;
+    private static final int TIME_SLIDE = 800;
+    private static final int TIME_HOLD2 = 3300;
+    private static final int TIME_SLIDE2 = 4100;
 
     private static final int ANIM_BOTH = 0;
     private static final int ANIM_FLASH = 1;
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 4d01bc3..245ef59 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -2216,7 +2216,6 @@
         initializeMiscControls();
         showTimeLapseUI(mCaptureTimeLapse);
         initializeVideoSnapshot();
-        resizeForPreviewAspectRatio();
 
         // from onResume()
         showVideoSnapshotUI(false);
@@ -2279,6 +2278,7 @@
     }
 
     private void updateOnScreenIndicators() {
+        if (mParameters == null) return;
         updateFlashOnScreenIndicator(mParameters.getFlashMode());
     }
 
@@ -2436,7 +2436,7 @@
     }
 
     private void initializeZoom() {
-        if (!mParameters.isZoomSupported()) return;
+        if (mParameters == null || !mParameters.isZoomSupported()) return;
         mZoomMax = mParameters.getMaxZoom();
         mZoomRatios = mParameters.getZoomRatios();
         // Currently we use immediate zoom for fast zooming to get better UX and
@@ -2448,6 +2448,7 @@
     }
 
     private void initializeVideoSnapshot() {
+        if (mParameters == null) return;
         if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
             mActivity.setSingleTapUpListener(mPreviewFrameLayout);
             // Show the tap to focus toast if this is the first start.
@@ -2462,6 +2463,7 @@
     }
 
     void showVideoSnapshotUI(boolean enabled) {
+        if (mParameters == null) return;
         if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
             if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) {
                 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index c9cce81..d960942 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -38,9 +38,9 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLRootView;
 import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
@@ -222,16 +222,10 @@
         } finally {
             mGLRootView.unlockRenderThread();
         }
-        clearBitmapPool(MediaItem.getMicroThumbPool());
-        clearBitmapPool(MediaItem.getThumbPool());
-
+        GalleryBitmapPool.getInstance().clear();
         MediaItem.getBytesBufferPool().clear();
     }
 
-    private static void clearBitmapPool(BitmapPool pool) {
-        if (pool != null) pool.clear();
-    }
-
     @Override
     protected void onDestroy() {
         super.onDestroy();
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index faff146..d409315 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -23,7 +23,6 @@
 
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.ContentListener;
 import com.android.gallery3d.data.LocalMediaItem;
 import com.android.gallery3d.data.MediaItem;
@@ -550,8 +549,8 @@
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) {
-        return mTileProvider.getTile(level, x, y, tileSize, pool);
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
+        return mTileProvider.getTile(level, x, y, tileSize);
     }
 
     @Override
diff --git a/src/com/android/gallery3d/data/BitmapPool.java b/src/com/android/gallery3d/data/BitmapPool.java
deleted file mode 100644
index 5bc6d67..0000000
--- a/src/com/android/gallery3d/data/BitmapPool.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.data;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.common.Utils;
-
-import java.util.ArrayList;
-
-public class BitmapPool {
-    @SuppressWarnings("unused")
-    private static final String TAG = "BitmapPool";
-
-    private final ArrayList<Bitmap> mPool;
-    private final int mPoolLimit;
-
-    // mOneSize is true if the pool can only cache Bitmap with one size.
-    private final boolean mOneSize;
-    private final int mWidth, mHeight;  // only used if mOneSize is true
-
-    // Construct a BitmapPool which caches bitmap with the specified size.
-    public BitmapPool(int width, int height, int poolLimit) {
-        mWidth = width;
-        mHeight = height;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = true;
-    }
-
-    // Construct a BitmapPool which caches bitmap with any size;
-    public BitmapPool(int poolLimit) {
-        mWidth = -1;
-        mHeight = -1;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = false;
-    }
-
-    // Get a Bitmap from the pool.
-    public synchronized Bitmap getBitmap() {
-        Utils.assertTrue(mOneSize);
-        int size = mPool.size();
-        return size > 0 ? mPool.remove(size - 1) : null;
-    }
-
-    // Get a Bitmap from the pool with the specified size.
-    public synchronized Bitmap getBitmap(int width, int height) {
-        Utils.assertTrue(!mOneSize);
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            Bitmap b = mPool.get(i);
-            if (b.getWidth() == width && b.getHeight() == height) {
-                return mPool.remove(i);
-            }
-        }
-        return null;
-    }
-
-    // Put a Bitmap into the pool, if the Bitmap has a proper size. Otherwise
-    // the Bitmap will be recycled. If the pool is full, an old Bitmap will be
-    // recycled.
-    public void recycle(Bitmap bitmap) {
-        if (bitmap == null) return;
-        if (mOneSize && ((bitmap.getWidth() != mWidth) ||
-                (bitmap.getHeight() != mHeight))) {
-            bitmap.recycle();
-            return;
-        }
-        synchronized (this) {
-            if (mPool.size() >= mPoolLimit) mPool.remove(0);
-            mPool.add(bitmap);
-        }
-    }
-
-    public synchronized void clear() {
-        mPool.clear();
-    }
-
-    public boolean isOneSize() {
-        return mOneSize;
-    }
-}
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index 8fcf400..38865e9 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -16,13 +16,13 @@
 
 package com.android.gallery3d.data;
 
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
 
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.app.StitchingChangeListener;
-import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
 import com.android.gallery3d.data.MediaSet.ItemConsumer;
@@ -65,6 +65,11 @@
     // to prevent concurrency issue.
     public static final Object LOCK = new Object();
 
+    public static DataManager from(Context context) {
+        GalleryApp app = (GalleryApp) context.getApplicationContext();
+        return app.getDataManager();
+    }
+
     private static final String TAG = "DataManager";
 
     // This is the path for the media set seen by the user at top level.
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index 4d3c996..fa70915 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -28,6 +28,7 @@
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.ui.Log;
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -246,21 +247,17 @@
     }
 
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc, byte[] data, int offset,
-            int length, BitmapFactory.Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, data, offset, length, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset,
+            int length, BitmapFactory.Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, data, offset, length, options) : null;
+                ? findCachedBitmap(jc, data, offset, length, options) : null;
         try {
             Bitmap bitmap = decode(jc, data, offset, length, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -268,7 +265,7 @@
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, data, offset, length, options);
         }
@@ -277,21 +274,17 @@
     // This is the same as the method above except the source data comes
     // from a file descriptor instead of a byte array.
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc,
-            FileDescriptor fileDescriptor, Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, fileDescriptor, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc,
+            FileDescriptor fileDescriptor, Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, fileDescriptor, options) : null;
+                ? findCachedBitmap(jc, fileDescriptor, options) : null;
         try {
             Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -299,23 +292,21 @@
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, fileDescriptor, options);
         }
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            byte[] data, int offset, int length, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, byte[] data,
+            int offset, int length, Options options) {
         decodeBounds(jc, data, offset, length, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            FileDescriptor fileDescriptor, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor,
+            Options options) {
         decodeBounds(jc, fileDescriptor, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 }
diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java
index 3f937e3..4756149 100644
--- a/src/com/android/gallery3d/data/ImageCacheRequest.java
+++ b/src/com/android/gallery3d/data/ImageCacheRequest.java
@@ -60,13 +60,11 @@
                 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                 Bitmap bitmap;
                 if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getMicroThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 } else {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 }
                 if (bitmap == null && !jc.isCancelled()) {
                     Log.w(TAG, "decode cached failed " + debugTag());
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index 19084d4..59ea865 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -42,15 +42,10 @@
     private static final int BYTESBUFFER_SIZE = 200 * 1024;
 
     private static int sMicrothumbnailTargetSize = 200;
-    private static BitmapPool sMicroThumbPool;
     private static final BytesBufferPool sMicroThumbBufferPool =
             new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
 
     private static int sThumbnailTargetSize = 640;
-    private static final BitmapPool sThumbPool =
-            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-            ? new BitmapPool(4)
-            : null;
 
     // TODO: fix default value for latlng and change this.
     public static final double INVALID_LATLNG = 0f;
@@ -126,33 +121,14 @@
         }
     }
 
-    public static BitmapPool getMicroThumbPool() {
-        if (ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY && sMicroThumbPool == null) {
-            initializeMicroThumbPool();
-        }
-        return sMicroThumbPool;
-    }
-
-    public static BitmapPool getThumbPool() {
-        return sThumbPool;
-    }
-
     public static BytesBufferPool getBytesBufferPool() {
         return sMicroThumbBufferPool;
     }
 
-    private static void initializeMicroThumbPool() {
-        sMicroThumbPool =
-                ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-                ? new BitmapPool(sMicrothumbnailTargetSize, sMicrothumbnailTargetSize, 16)
-                : null;
-    }
-
     public static void setThumbnailSizes(int size, int microSize) {
         sThumbnailTargetSize = size;
         if (sMicrothumbnailTargetSize != microSize) {
             sMicrothumbnailTargetSize = microSize;
-            initializeMicroThumbPool();
         }
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index f7147ea..fd30ac0 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -71,6 +71,7 @@
 import com.android.gallery3d.filtershow.imageshow.MasterImage;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 import com.android.gallery3d.filtershow.provider.SharedImageProvider;
+import com.android.gallery3d.filtershow.tools.BitmapTask;
 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
 import com.android.gallery3d.filtershow.ui.FilterIconButton;
 import com.android.gallery3d.filtershow.ui.FramedTextButton;
@@ -151,6 +152,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        ImageFilter.setActivityForMemoryToasts(this);
         setResources();
 
         Resources res = getResources();
@@ -519,6 +521,9 @@
         if (mLoadBitmapTask != null) {
             mLoadBitmapTask.cancel(false);
         }
+        MasterImage.reset();
+        FilteringPipeline.reset();
+        ImageFilter.resetStatics();
         super.onDestroy();
     }
 
@@ -814,7 +819,6 @@
     }
 
     public void hideImageViews() {
-        mImageShow.setShowControls(false); // reset
         for (View view : mImageViews) {
             view.setVisibility(View.GONE);
         }
@@ -1090,11 +1094,7 @@
                     mCropExtras.getOutputFormat(), this);
         }
         if (mSaveAsWallpaper) {
-            try {
-                WallpaperManager.getInstance(this).setBitmap(filtered);
-            } catch (IOException e) {
-                Log.w(LOGTAG, "fail to set wall paper", e);
-            }
+            setWallpaperInBackground(filtered);
         }
         if (mReturnAsExtra) {
             if (filtered != null) {
@@ -1117,6 +1117,28 @@
         }
     }
 
+    void setWallpaperInBackground(final Bitmap bmap) {
+        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+        BitmapTask.Callbacks<FilterShowActivity> cb = new BitmapTask.Callbacks<FilterShowActivity>() {
+            @Override
+            public void onComplete(Bitmap result) {}
+
+            @Override
+            public void onCancel() {}
+
+            @Override
+            public Bitmap onExecute(FilterShowActivity param) {
+                try {
+                    WallpaperManager.getInstance(param).setBitmap(bmap);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "fail to set wall paper", e);
+                }
+                return null;
+            }
+        };
+        (new BitmapTask<FilterShowActivity>(cb)).execute(this);
+    }
+
     public void done() {
         if (mOutputted) {
             hideSavingProgress();
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
index 18a9585..5bda246 100644
--- a/src/com/android/gallery3d/filtershow/PanelController.java
+++ b/src/com/android/gallery3d/filtershow/PanelController.java
@@ -375,18 +375,20 @@
         ImageShow image = null;
         mActivity.hideImageViews();
         for (View view : mImageViews) {
+            image = (ImageShow) view;
             if (view.getId() == id) {
                 view.setVisibility(View.VISIBLE);
-                image = (ImageShow) view;
+                image.select();
             } else {
                 view.setVisibility(View.GONE);
+                image.unselect();
             }
         }
         return image;
     }
 
     public void showDefaultImageView() {
-        showImageView(R.id.imageShow).setShowControls(false);
+        showImageView(R.id.imageShow);
         MasterImage.getImage().setCurrentFilter(null);
         MasterImage.getImage().setCurrentFilterRepresentation(null);
     }
@@ -498,7 +500,7 @@
         }
         mUtilityPanel.hideAccessoryViews();
 
-        if (view instanceof FilterIconButton) {
+        if (view instanceof FilterIconButton && view.getId() != R.id.applyEffect) {
             mCurrentEditor = null;
             FilterIconButton component = (FilterIconButton) view;
             FilterRepresentation representation = component.getFilterRepresentation();
@@ -515,7 +517,6 @@
                         mCurrentImage = showImageView(representation.getEditorId());
                     }
                 }
-                mCurrentImage.setShowControls(representation.showEditingControls());
                 mUtilityPanel.setShowParameter(representation.showParameterValue());
 
                 mCurrentImage.select();
@@ -533,7 +534,7 @@
 
         switch (view.getId()) {
             case R.id.tinyplanetButton: {
-                mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true);
+                mCurrentImage = showImageView(R.id.imageTinyPlanet);
                 String ename = mCurrentImage.getContext().getString(R.string.tinyplanet);
                 mUtilityPanel.setEffectName(ename);
                 if (!mDisableFilterButtons) {
@@ -556,8 +557,8 @@
                 if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) {
                     ((ImageCrop) mCurrentImage).clear();
                     mUtilityPanel.firstTimeCropDisplayed = false;
+                    ((ImageCrop) mCurrentImage).setFixedAspect(mFixedAspect);
                 }
-                ((ImageCrop) mCurrentImage).setFixedAspect(mFixedAspect);
                 break;
             }
             case R.id.rotateButton: {
diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
index 419abe8..0af4063 100644
--- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
@@ -31,7 +31,7 @@
 
 public class FilteringPipeline implements Handler.Callback {
 
-    private final static FilteringPipeline gPipeline = new FilteringPipeline();
+    private static FilteringPipeline sPipeline;
     private static final String LOGTAG = "FilteringPipeline";
     private ImagePreset mPreviousGeometryPreset = null;
     private ImagePreset mPreviousFiltersPreset = null;
@@ -48,6 +48,7 @@
     private final static int NEW_RENDERING_REQUEST = 1;
     private final static int COMPUTE_PRESET = 2;
     private final static int COMPUTE_RENDERING_REQUEST = 3;
+    private final static int COMPUTE_PARTIAL_RENDERING_REQUEST = 4;
 
     private Handler mProcessingHandler = null;
     private final Handler mUIHandler = new Handler() {
@@ -81,7 +82,13 @@
                 mUIHandler.sendMessage(uimsg);
                 break;
             }
-            case COMPUTE_RENDERING_REQUEST: {
+            case COMPUTE_RENDERING_REQUEST:
+            case COMPUTE_PARTIAL_RENDERING_REQUEST: {
+                if (msg.what == COMPUTE_PARTIAL_RENDERING_REQUEST) {
+                    if (mProcessingHandler.hasMessages(COMPUTE_PARTIAL_RENDERING_REQUEST)) {
+                        return false;
+                    }
+                }
                 RenderingRequest request = (RenderingRequest) msg.obj;
                 render(request);
                 Message uimsg = mUIHandler.obtainMessage(NEW_RENDERING_REQUEST);
@@ -95,6 +102,7 @@
 
     private static float RESIZE_FACTOR = 0.8f;
     private static float MAX_PROCESS_TIME = 100; // in ms
+    private static long HIRES_DELAY = 100; // in ms
     private float mResizeFactor = 1.0f;
     private long mResizeTime = 0;
 
@@ -109,7 +117,10 @@
     }
 
     public static FilteringPipeline getPipeline() {
-        return gPipeline;
+        if (sPipeline == null) {
+            sPipeline = new FilteringPipeline();
+        }
+        return sPipeline;
     }
 
     public synchronized void setOriginal(Bitmap bitmap) {
@@ -165,9 +176,17 @@
         if (mOriginalAllocation == null) {
             return;
         }
-        Message msg = mProcessingHandler.obtainMessage(COMPUTE_RENDERING_REQUEST);
+        int type = COMPUTE_RENDERING_REQUEST;
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            type = COMPUTE_PARTIAL_RENDERING_REQUEST;
+        }
+        Message msg = mProcessingHandler.obtainMessage(type);
         msg.obj = request;
-        mProcessingHandler.sendMessage(msg);
+        if (type == COMPUTE_PARTIAL_RENDERING_REQUEST) {
+            mProcessingHandler.sendMessageDelayed(msg, HIRES_DELAY);
+        } else {
+            mProcessingHandler.sendMessage(msg);
+        }
     }
 
     public synchronized void updatePreviewBuffer() {
@@ -210,11 +229,15 @@
         if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
             return "GEOMETRY_RENDERING";
         }
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            return "PARTIAL_RENDERING";
+        }
         return "UNKNOWN TYPE!";
     }
 
     private void render(RenderingRequest request) {
-        if (request.getBitmap() == null
+        if ((request.getType() != RenderingRequest.PARTIAL_RENDERING
+                && request.getBitmap() == null)
                 || request.getImagePreset() == null) {
             return;
         }
@@ -225,11 +248,21 @@
         Bitmap bitmap = request.getBitmap();
         ImagePreset preset = request.getImagePreset();
         setPresetParameters(preset);
+
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            bitmap = MasterImage.getImage().getImageLoader().getScaleOneImageForPreset(null, preset, request.getBounds(), request.getDestination(), false);
+            if (bitmap == null) {
+                return;
+            }
+            bitmap = preset.applyGeometry(bitmap);
+        }
+
         if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
             FiltersManager.getManager().resetBitmapsRS();
         }
 
-        if (request.getType() != RenderingRequest.ICON_RENDERING) {
+        if (request.getType() != RenderingRequest.ICON_RENDERING
+                && request.getType() != RenderingRequest.PARTIAL_RENDERING) {
             updateOriginalAllocation(preset);
         }
         if (DEBUG) {
@@ -243,9 +276,11 @@
         } else if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
             mFiltersOnlyOriginalAllocation.copyTo(bitmap);
         }
+
         if (request.getType() == RenderingRequest.FULL_RENDERING
                 || request.getType() == RenderingRequest.FILTERS_RENDERING
-                || request.getType() == RenderingRequest.ICON_RENDERING) {
+                || request.getType() == RenderingRequest.ICON_RENDERING
+                || request.getType() == RenderingRequest.PARTIAL_RENDERING) {
             Bitmap bmp = preset.apply(bitmap);
             request.setBitmap(bmp);
         }
@@ -305,4 +340,8 @@
     public float getPreviewScaleFactor() {
         return mPreviewScaleFactor;
     }
+
+    public static void reset() {
+        sPipeline = null;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index 698992a..b502a2f 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -62,6 +62,8 @@
 import java.util.Vector;
 import java.util.concurrent.locks.ReentrantLock;
 
+
+// TODO: this class has waaaay to much bitmap copying.  Cleanup.
 public class ImageLoader {
 
     private static final String LOGTAG = "ImageLoader";
@@ -91,6 +93,7 @@
     public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE;
     public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
 
+    private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
     private Context mContext = null;
     private Uri mUri = null;
 
@@ -264,12 +267,12 @@
                 bitmap.getHeight(), matrix, true);
     }
 
-    private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
+    private Bitmap loadRegionBitmap(Uri uri, BitmapFactory.Options options, Rect bounds) {
         InputStream is = null;
         try {
             is = mContext.getContentResolver().openInputStream(uri);
             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
-            return decoder.decodeRegion(bounds, null);
+            return decoder.decodeRegion(bounds, options);
         } catch (FileNotFoundException e) {
             Log.e(LOGTAG, "FileNotFoundException: " + uri);
         } catch (Exception e) {
@@ -369,21 +372,36 @@
     // FIXME: this currently does the loading + filtering on the UI thread --
     // need to move this to a background thread.
     public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
-            boolean force) {
+                                            Rect destination, boolean force) {
         mLoadingLock.lock();
         Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
         if (force || bmp == null) {
-            bmp = loadRegionBitmap(mUri, bounds);
+            BitmapFactory.Options options = null;
+            if (destination != null) {
+                options = new BitmapFactory.Options();
+                if (bounds.width() > destination.width()) {
+                    int sampleSize = 1;
+                    int w = bounds.width();
+                    while (w > destination.width()) {
+                        sampleSize *= 2;
+                        w /= sampleSize;
+                    }
+                    options.inSampleSize = sampleSize;
+                }
+            }
+            bmp = loadRegionBitmap(mUri, options, bounds);
+            if (destination != null) {
+                mLoadingLock.unlock();
+                return bmp;
+            }
             if (bmp != null) {
-                // TODO: this workaround for RS might not be needed ultimately
-                Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
                 float scaleFactor = imagePreset.getScaleFactor();
                 imagePreset.setScaleFactor(1.0f);
-                bmp2 = imagePreset.apply(bmp2);
+                bmp = imagePreset.apply(bmp);
                 imagePreset.setScaleFactor(scaleFactor);
-                mZoomCache.setImage(imagePreset, bounds, bmp2);
+                mZoomCache.setImage(imagePreset, bounds, bmp);
                 mLoadingLock.unlock();
-                return bmp2;
+                return bmp;
             }
         }
         mLoadingLock.unlock();
@@ -406,22 +424,16 @@
 
     public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) {
         BitmapFactory.Options options = new BitmapFactory.Options();
+        return loadMutableBitmap(context, sourceUri, options);
+    }
+
+    public static Bitmap loadMutableBitmap(Context context, Uri sourceUri,
+            BitmapFactory.Options options) {
         // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
         // exist)
         options.inMutable = true;
 
-        InputStream is = null;
-        Bitmap bitmap = null;
-        try {
-            is = context.getContentResolver().openInputStream(sourceUri);
-            bitmap = BitmapFactory.decodeStream(is, null, options);
-        } catch (FileNotFoundException e) {
-            Log.w(LOGTAG, "could not load bitmap ", e);
-            is = null;
-            bitmap = null;
-        } finally {
-            Utils.closeSilently(is);
-        }
+        Bitmap bitmap = decodeUriWithBackouts(context, sourceUri, options);
         if (bitmap == null) {
             return null;
         }
@@ -430,6 +442,81 @@
         return bitmap;
     }
 
+    public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
+            BitmapFactory.Options options) {
+        boolean noBitmap = true;
+        int num_tries = 0;
+        InputStream is = getInputStream(context, sourceUri);
+
+        if (options.inSampleSize < 1) {
+            options.inSampleSize = 1;
+        }
+        // Stopgap fix for low-memory devices.
+        Bitmap bmap = null;
+        while (noBitmap) {
+            if (is == null) {
+                return null;
+            }
+            try {
+                // Try to decode, downsample if low-memory.
+                bmap = BitmapFactory.decodeStream(is, null, options);
+                noBitmap = false;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+                    throw e;
+                }
+                is = null;
+                bmap = null;
+                System.gc();
+                is = getInputStream(context, sourceUri);
+                options.inSampleSize *= 2;
+            }
+        }
+        Utils.closeSilently(is);
+        return bmap;
+    }
+
+    private static InputStream getInputStream(Context context, Uri sourceUri) {
+        InputStream is = null;
+        try {
+            is = context.getContentResolver().openInputStream(sourceUri);
+        } catch (FileNotFoundException e) {
+            Log.w(LOGTAG, "could not load bitmap ", e);
+            Utils.closeSilently(is);
+            is = null;
+        }
+        return is;
+    }
+
+    public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
+            int id) {
+        boolean noBitmap = true;
+        int num_tries = 0;
+        if (options.inSampleSize < 1) {
+            options.inSampleSize = 1;
+        }
+        // Stopgap fix for low-memory devices.
+        Bitmap bmap = null;
+        while (noBitmap) {
+            try {
+                // Try to decode, downsample if low-memory.
+                bmap = BitmapFactory.decodeResource(
+                        res, id, options);
+                noBitmap = false;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+                    throw e;
+                }
+                bmap = null;
+                System.gc();
+                options.inSampleSize *= 2;
+            }
+        }
+        return bmap;
+    }
+
     public void returnFilteredResult(ImagePreset preset,
             final FilterShowActivity filterShowActivity) {
         preset.setQuality(ImagePreset.QUALITY_FINAL);
@@ -451,13 +538,36 @@
                 if (param == null || mUri == null) {
                     return null;
                 }
-                Bitmap bitmap = loadMutableBitmap(mContext, mUri);
-                if (bitmap == null) {
-                    Log.w(LOGTAG, "Failed to save image!");
-                    return null;
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                boolean noBitmap = true;
+                int num_tries = 0;
+                if (options.inSampleSize < 1) {
+                    options.inSampleSize = 1;
                 }
-                bitmap = param.applyGeometry(bitmap);
-                return param.apply(bitmap);
+                Bitmap bitmap = null;
+                // Stopgap fix for low-memory devices.
+                while (noBitmap) {
+                    try {
+                        // Try to do bitmap operations, downsample if low-memory
+                        bitmap = loadMutableBitmap(mContext, mUri, options);
+                        if (bitmap == null) {
+                            Log.w(LOGTAG, "Failed to save image!");
+                            return null;
+                        }
+                        bitmap = param.applyGeometry(bitmap);
+                        bitmap = param.apply(bitmap);
+                        noBitmap = false;
+                    } catch (java.lang.OutOfMemoryError e) {
+                        // Try 5 times before failing for good.
+                        if (++num_tries >= 5) {
+                            throw e;
+                        }
+                        bitmap = null;
+                        System.gc();
+                        options.inSampleSize *= 2;
+                    }
+                }
+                return bitmap;
             }
         };
 
diff --git a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
index 1e9f6b8..3ec74e2 100644
--- a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
+++ b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
@@ -17,6 +17,7 @@
 package com.android.gallery3d.filtershow.cache;
 
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import com.android.gallery3d.app.Log;
 import com.android.gallery3d.filtershow.imageshow.MasterImage;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -27,29 +28,46 @@
     private Bitmap mBitmap = null;
     private ImagePreset mImagePreset = null;
     private RenderingRequestCaller mCaller = null;
+    private Rect mBounds = null;
+    private Rect mDestination = null;
     private int mType = FULL_RENDERING;
-    public static int FULL_RENDERING = 0;
-    public static int FILTERS_RENDERING = 1;
-    public static int GEOMETRY_RENDERING = 2;
-    public static int ICON_RENDERING = 3;
+    public static final int FULL_RENDERING = 0;
+    public static final int FILTERS_RENDERING = 1;
+    public static final int GEOMETRY_RENDERING = 2;
+    public static final int ICON_RENDERING = 3;
+    public static final int PARTIAL_RENDERING = 4;
     private static final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
 
+    public static void post(Bitmap source, ImagePreset preset, int type, RenderingRequestCaller caller) {
+        RenderingRequest.post(source, preset, type, caller, null, null);
+    }
+
     public static void post(Bitmap source, ImagePreset preset, int type,
-                            RenderingRequestCaller caller) {
-        if (source == null || preset == null || caller == null) {
+                            RenderingRequestCaller caller, Rect bounds, Rect destination) {
+        if ((type != PARTIAL_RENDERING && source == null) || preset == null || caller == null) {
             Log.v(LOGTAG, "something null: source: " + source + " or preset: " + preset + " or caller: " + caller);
             return;
         }
         RenderingRequest request = new RenderingRequest();
         Bitmap bitmap = null;
-        if (type == FULL_RENDERING || type == GEOMETRY_RENDERING || type == ICON_RENDERING) {
+        if (type == FULL_RENDERING
+                || type == GEOMETRY_RENDERING
+                || type == ICON_RENDERING) {
             bitmap = preset.applyGeometry(source);
-        } else {
+        } else if (type != PARTIAL_RENDERING) {
             bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), mConfig);
         }
+
         request.setBitmap(bitmap);
         ImagePreset passedPreset = new ImagePreset(preset);
         passedPreset.setImageLoader(MasterImage.getImage().getImageLoader());
+
+        if (type == PARTIAL_RENDERING) {
+            request.setBounds(bounds);
+            request.setDestination(destination);
+            passedPreset.setPartialRendering(true, bounds);
+        }
+
         request.setImagePreset(passedPreset);
         request.setType(type);
         request.setCaller(caller);
@@ -103,4 +121,20 @@
     public void setCaller(RenderingRequestCaller caller) {
         mCaller = caller;
     }
+
+    public Rect getBounds() {
+        return mBounds;
+    }
+
+    public void setBounds(Rect bounds) {
+        mBounds = bounds;
+    }
+
+    public Rect getDestination() {
+        return mDestination;
+    }
+
+    public void setDestination(Rect destination) {
+        mDestination = destination;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index 43660d6..377bd2b 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -42,6 +42,7 @@
         filters.add(new ImageFilterVignette());
         filters.add(new ImageFilterContrast());
         filters.add(new ImageFilterShadows());
+        filters.add(new ImageFilterHighlights());
         filters.add(new ImageFilterVibrance());
         filters.add(new ImageFilterSharpen());
         filters.add(new ImageFilterCurves());
@@ -89,6 +90,7 @@
         representations.add(getRepresentation(ImageFilterVignette.class));
         representations.add(getRepresentation(ImageFilterContrast.class));
         representations.add(getRepresentation(ImageFilterShadows.class));
+        representations.add(getRepresentation(ImageFilterHighlights.class));
         representations.add(getRepresentation(ImageFilterVibrance.class));
         representations.add(getRepresentation(ImageFilterSharpen.class));
         representations.add(getRepresentation(ImageFilterCurves.class));
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
index 6c83170..3511c67 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
@@ -19,6 +19,7 @@
         setShowEditingControls(false);
         setShowParameterValue(false);
         setShowUtilityPanel(true);
+        setSupportsPartialRendering(true);
         for (int i = 0; i < mSplines.length; i++) {
             mSplines[i] = new Spline();
             mSplines[i].reset();
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
index e41f0a6..8b8504b 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
@@ -150,6 +150,10 @@
         mCurrent = null;
     }
 
+    public void clearCurrentSection() {
+        mCurrent = null;
+    }
+
     public void clear() {
         mCurrent = null;
         mDrawing.clear();
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
index 859bf32..d4128dc 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
@@ -37,6 +37,7 @@
         setShowEditingControls(false);
         setShowParameterValue(false);
         setShowUtilityPanel(false);
+        setSupportsPartialRendering(true);
     }
 
     public String toString() {
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
index 513cdcd..83f2a1b 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -25,6 +25,7 @@
     private String mName;
     private int mPriority = TYPE_NORMAL;
     private Class mFilterClass;
+    private boolean mSupportsPartialRendering = false;
     private int mTextId = 0;
     private int mEditorId = BasicEditor.ID;
     private int mButtonId = 0;
@@ -52,6 +53,7 @@
         representation.setName(getName());
         representation.setPriority(getPriority());
         representation.setFilterClass(getFilterClass());
+        representation.setSupportsPartialRendering(supportsPartialRendering());
         representation.setTextId(getTextId());
         representation.setEditorId(getEditorId());
         representation.setButtonId(getButtonId());
@@ -70,6 +72,7 @@
         if (representation.mFilterClass == representation.mFilterClass
                 && representation.mName.equalsIgnoreCase(mName)
                 && representation.mPriority == mPriority
+                && representation.mSupportsPartialRendering == mSupportsPartialRendering
                 && representation.mTextId == mTextId
                 && representation.mEditorId == mEditorId
                 && representation.mButtonId == mButtonId
@@ -106,6 +109,14 @@
         return false;
     }
 
+    public boolean supportsPartialRendering() {
+        return mSupportsPartialRendering;
+    }
+
+    public void setSupportsPartialRendering(boolean value) {
+        mSupportsPartialRendering = value;
+    }
+
     public void useParametersFrom(FilterRepresentation a) {
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index 614c6a0..1b7a367 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -18,8 +18,10 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.widget.Toast;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
 import com.android.gallery3d.filtershow.editors.BasicEditor;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -31,6 +33,30 @@
     protected String mName = "Original";
     private final String LOGTAG = "ImageFilter";
 
+    // TODO: Temporary, for dogfood note memory issues with toasts for better
+    // feedback. Remove this when filters actually work in low memory
+    // situations.
+    private static FilterShowActivity sActivity = null;
+
+    public static void setActivityForMemoryToasts(FilterShowActivity activity) {
+        sActivity = activity;
+    }
+
+    public static void resetStatics() {
+        sActivity = null;
+    }
+
+    public void displayLowMemoryToast() {
+        if (sActivity != null) {
+            sActivity.runOnUiThread(new Runnable() {
+                public void run() {
+                    Toast.makeText(sActivity, "Memory too low for filter " + getName() +
+                            ", please file a bug report", Toast.LENGTH_SHORT).show();
+                }
+            });
+        }
+    }
+
     public void setName(String name) {
         mName = name;
     }
@@ -45,8 +71,8 @@
     }
 
     /**
-     * Called on small bitmaps to create button icons for each filter.
-     * Override this to provide filter-specific button icons.
+     * Called on small bitmaps to create button icons for each filter. Override
+     * this to provide filter-specific button icons.
      */
     public Bitmap iconApply(Bitmap bitmap, float scaleFactor, int quality) {
         return apply(bitmap, scaleFactor, quality);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
index c92ac01..a4626cd 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
@@ -36,6 +36,7 @@
         representation.setMinimum(-180);
         representation.setTextId(R.string.bwfilter);
         representation.setButtonId(R.id.bwfilterButton);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
index 2f94e3d..2097f0d 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
@@ -37,6 +37,7 @@
         representation.setMinimum(-100);
         representation.setMaximum(100);
         representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
index 55c7095..46a9a29 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
@@ -32,6 +32,7 @@
         representation.setFilterClass(ImageFilterEdge.class);
         representation.setTextId(R.string.edge);
         representation.setButtonId(R.id.edgeButton);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
index 7a8df71..b0b0b2d 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
@@ -36,6 +36,7 @@
         representation.setMinimum(-100);
         representation.setMaximum(100);
         representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
new file mode 100644
index 0000000..12f032d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class ImageFilterHighlights extends SimpleImageFilter {
+    private static final String LOGTAG = "ImageFilterVignette";
+
+    public ImageFilterHighlights() {
+        mName = "Highlights";
+    }
+
+    SplineMath mSpline = new SplineMath(5);
+    double[] mHighlightCurve = { 0.0, 0.32, 0.418, 0.476, 0.642 };
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Shadows");
+        representation.setFilterClass(ImageFilterHighlights.class);
+        representation.setTextId(R.string.highlight_recovery);
+        representation.setButtonId(R.id.highlightRecoveryButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float[] luminanceMap);
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
+        float p = getParameters().getValue();
+        double t = p/100.;
+        for (int i = 0; i < 5; i++) {
+            double x = i / 4.;
+            double y = mHighlightCurve[i] *t+x*(1-t);
+            mSpline.setPoint(i, x, y);
+        }
+
+        float[][] curve = mSpline.calculatetCurve(256);
+        float[] luminanceMap = new float[curve.length];
+        for (int i = 0; i < luminanceMap.length; i++) {
+            luminanceMap[i] = curve[i][1];
+        }
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        nativeApplyFilter(bitmap, w, h, luminanceMap);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
index 8c484c7..b1f9f73 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
@@ -39,6 +39,7 @@
         representation.setTextId(R.string.hue);
         representation.setButtonId(R.id.hueButton);
         representation.setEditorId(BasicEditor.ID);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
index f48bd04..6f785ef 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
@@ -45,6 +45,7 @@
         representation.setPreviewValue(4);
         representation.setTextId(R.string.kmeans);
         representation.setButtonId(R.id.kmeansButton);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
index 841c5c9..c256686 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
@@ -19,6 +19,7 @@
         representation.setShowEditingControls(false);
         representation.setShowParameterValue(false);
         representation.setEditorId(ImageOnlyEditor.ID);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index d529790..a3467ed 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -43,9 +43,11 @@
                 || (bitmap.getHeight() != sOldBitmap.getHeight())) {
             if (mInPixelsAllocation != null) {
                 mInPixelsAllocation.destroy();
+                mInPixelsAllocation = null;
             }
             if (mOutPixelsAllocation != null) {
                 mOutPixelsAllocation.destroy();
+                mOutPixelsAllocation = null;
             }
             Bitmap bitmapBuffer = bitmap.copy(mBitmapConfig, true);
             mOutPixelsAllocation = Allocation.createFromBitmap(mRS, bitmapBuffer,
@@ -87,6 +89,11 @@
             Log.e(LOGTAG, "Illegal argument? " + e);
         } catch (android.renderscript.RSRuntimeException e) {
             Log.e(LOGTAG, "RS runtime exception ? " + e);
+        } catch (java.lang.OutOfMemoryError e) {
+            // Many of the renderscript filters allocated large (>16Mb resources) in order to apply.
+            System.gc();
+            displayLowMemoryToast();
+            Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
         }
         return bitmap;
     }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
index 6cd8332..0febe49 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -37,6 +37,7 @@
         representation.setMinimum(-100);
         representation.setMaximum(100);
         representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
index e178239..fd67ee8 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
@@ -37,6 +37,7 @@
         representation.setMinimum(-100);
         representation.setMaximum(100);
         representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
index 9c99d57..8afa474 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
@@ -37,7 +37,8 @@
         representation.setTextId(R.string.sharpness);
         representation.setButtonId(R.id.sharpenButton);
         representation.setOverlayId(R.drawable.filtershow_button_colors_sharpen);
-        representation.setEditorId(R.id.imageZoom);
+        representation.setEditorId(R.id.imageShow);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
index a57af71..ea315d3 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -36,6 +36,7 @@
         representation.setMinimum(-100);
         representation.setMaximum(100);
         representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
index 2f48523..c4c293a 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
@@ -37,6 +37,7 @@
         representation.setShowEditingControls(false);
         representation.setShowParameterValue(false);
         representation.setEditorId(ImageOnlyEditor.ID);
+        representation.setSupportsPartialRendering(true);
         return representation;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/SplineMath.java b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
new file mode 100644
index 0000000..5b12d0a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
@@ -0,0 +1,166 @@
+package com.android.gallery3d.filtershow.filters;
+
+
+public class SplineMath {
+    double[][] mPoints = new double[6][2];
+    double[] mDerivatives;
+    SplineMath(int n) {
+        mPoints = new double[n][2];
+    }
+
+    public void setPoint(int index, double x, double y) {
+        mPoints[index][0] = x;
+        mPoints[index][1] = y;
+        mDerivatives = null;
+    }
+
+    public float[][] calculatetCurve(int n) {
+        float[][] curve = new float[n][2];
+        double[][] points = new double[mPoints.length][2];
+        for (int i = 0; i < mPoints.length; i++) {
+
+            points[i][0] = mPoints[i][0];
+            points[i][1] = mPoints[i][1];
+
+        }
+        double[] derivatives = solveSystem(points);
+        float start = (float) points[0][0];
+        float end = (float) (points[points.length - 1][0]);
+
+        curve[0][0] = (float) (points[0][0]);
+        curve[0][1] = (float) (points[0][1]);
+        int last = curve.length - 1;
+        curve[last][0] = (float) (points[points.length - 1][0]);
+        curve[last][1] = (float) (points[points.length - 1][1]);
+
+        for (int i = 0; i < curve.length; i++) {
+
+            double[] cur = null;
+            double[] next = null;
+            double x = start + i * (end - start) / (curve.length - 1);
+            int pivot = 0;
+            for (int j = 0; j < points.length - 1; j++) {
+                if (x >= points[j][0] && x <= points[j + 1][0]) {
+                    pivot = j;
+                }
+            }
+            cur = points[pivot];
+            next = points[pivot + 1];
+            if (x <= next[0]) {
+                double x1 = cur[0];
+                double x2 = next[0];
+                double y1 = cur[1];
+                double y2 = next[1];
+
+                // Use the second derivatives to apply the cubic spline
+                // equation:
+                double delta = (x2 - x1);
+                double delta2 = delta * delta;
+                double b = (x - x1) / delta;
+                double a = 1 - b;
+                double ta = a * y1;
+                double tb = b * y2;
+                double tc = (a * a * a - a) * derivatives[pivot];
+                double td = (b * b * b - b) * derivatives[pivot + 1];
+                double y = ta + tb + (delta2 / 6) * (tc + td);
+
+                curve[i][0] = (float) (x);
+                curve[i][1] = (float) (y);
+            } else {
+                curve[i][0] = (float) (next[0]);
+                curve[i][1] = (float) (next[1]);
+            }
+        }
+        return curve;
+    }
+
+    public double getValue(double x) {
+        double[] cur = null;
+        double[] next = null;
+        if (mDerivatives == null)
+            mDerivatives = solveSystem(mPoints);
+        int pivot = 0;
+        for (int j = 0; j < mPoints.length - 1; j++) {
+            pivot = j;
+            if (x <= mPoints[j][0]) {
+                break;
+            }
+        }
+        cur = mPoints[pivot];
+        next = mPoints[pivot + 1];
+        double x1 = cur[0];
+        double x2 = next[0];
+        double y1 = cur[1];
+        double y2 = next[1];
+
+        // Use the second derivatives to apply the cubic spline
+        // equation:
+        double delta = (x2 - x1);
+        double delta2 = delta * delta;
+        double b = (x - x1) / delta;
+        double a = 1 - b;
+        double ta = a * y1;
+        double tb = b * y2;
+        double tc = (a * a * a - a) * mDerivatives[pivot];
+        double td = (b * b * b - b) * mDerivatives[pivot + 1];
+        double y = ta + tb + (delta2 / 6) * (tc + td);
+
+        return y;
+
+    }
+
+    double[] solveSystem(double[][] points) {
+        int n = points.length;
+        double[][] system = new double[n][3];
+        double[] result = new double[n]; // d
+        double[] solution = new double[n]; // returned coefficients
+        system[0][1] = 1;
+        system[n - 1][1] = 1;
+        double d6 = 1.0 / 6.0;
+        double d3 = 1.0 / 3.0;
+
+        // let's create a tridiagonal matrix representing the
+        // system, and apply the TDMA algorithm to solve it
+        // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
+        for (int i = 1; i < n - 1; i++) {
+            double deltaPrevX = points[i][0] - points[i - 1][0];
+            double deltaX = points[i + 1][0] - points[i - 1][0];
+            double deltaNextX = points[i + 1][0] - points[i][0];
+            double deltaNextY = points[i + 1][1] - points[i][1];
+            double deltaPrevY = points[i][1] - points[i - 1][1];
+            system[i][0] = d6 * deltaPrevX; // a_i
+            system[i][1] = d3 * deltaX; // b_i
+            system[i][2] = d6 * deltaNextX; // c_i
+            result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i
+        }
+
+        // Forward sweep
+        for (int i = 1; i < n; i++) {
+            // m = a_i/b_i-1
+            double m = system[i][0] / system[i - 1][1];
+            // b_i = b_i - m(c_i-1)
+            system[i][1] = system[i][1] - m * system[i - 1][2];
+            // d_i = d_i - m(d_i-1)
+            result[i] = result[i] - m * result[i - 1];
+        }
+
+        // Back substitution
+        solution[n - 1] = result[n - 1] / system[n - 1][1];
+        for (int i = n - 2; i >= 0; --i) {
+            solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1];
+        }
+        return solution;
+    }
+
+    public static void main(String[] args) {
+        SplineMath s = new SplineMath(10);
+        for (int i = 0; i < 10; i++) {
+            s.setPoint(i, i, i);
+        }
+        float[][] curve = s.calculatetCurve(40);
+
+        for (int j = 0; j < curve.length; j++) {
+            System.out.println(curve[j][0] + "," + curve[j][1]);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
index c46baf5..479652c 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
@@ -27,11 +27,13 @@
     public ImageDraw(Context context, AttributeSet attrs) {
         super(context, attrs);
         resetParameter();
+        super.setOriginalDisabled(true);
     }
 
     public ImageDraw(Context context) {
         super(context);
         resetParameter();
+        super.setOriginalDisabled(true);
     }
 
     public void setEditor(EditorDraw editorDraw) {
@@ -82,27 +84,32 @@
     float[] mTmpPoint = new float[2]; // so we do not malloc
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        super.onTouchEvent(event);
-
-        if (event.getPointerCount() != 1) {
-            return true;
+        boolean ret = super.onTouchEvent(event);
+        if (event.getPointerCount() > 1) {
+            if (mFRep.getCurrentDrawing() != null) {
+                mFRep.clearCurrentSection();
+                mEditorDraw.commitLocalRepresentation();
+            }
+            return ret;
         }
-
-        if (didFinishScalingOperation()) {
-            return true;
+        if (event.getAction() != MotionEvent.ACTION_DOWN) {
+            if (mFRep.getCurrentDrawing() == null) {
+                return ret;
+            }
         }
 
         ImageFilterDraw filter = (ImageFilterDraw) getCurrentFilter();
 
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            calcScreenMapping();
             mTmpPoint[0] = event.getX();
             mTmpPoint[1] = event.getY();
             mToOrig.mapPoints(mTmpPoint);
             mFRep.startNewSection(mType, mCurrentColor, mCurrentSize, mTmpPoint[0], mTmpPoint[1]);
-
         }
 
         if (event.getAction() == MotionEvent.ACTION_MOVE) {
+
             int historySize = event.getHistorySize();
             final int pointerCount = event.getPointerCount();
             for (int h = 0; h < historySize; h++) {
@@ -127,19 +134,10 @@
         return true;
     }
 
-    Matrix mRotateToScreen;
-    Matrix mToScreen;
-    Matrix mToOrig = new Matrix();
+    Matrix mRotateToScreen = new Matrix();
+    Matrix mToOrig;
     private void calcScreenMapping() {
-
-        GeometryMetadata geo = getImagePreset().mGeoData;
-        mToScreen = geo.getOriginalToScreen(false,
-                mImageLoader.getOriginalBounds().width(),
-                mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
-        mRotateToScreen = geo.getOriginalToScreen(true,
-                mImageLoader.getOriginalBounds().width(),
-                mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
-        mRotateToScreen.invert(mToOrig);
+        mToOrig = getScreenToImageMatrix(true);
         mToOrig.invert(mRotateToScreen);
     }
 
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 7922398..39e0cc8 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -26,23 +26,19 @@
 import android.view.GestureDetector.OnDoubleTapListener;
 import android.view.GestureDetector.OnGestureListener;
 import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.gallery3d.filtershow.FilterShowActivity;
 import com.android.gallery3d.filtershow.PanelController;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
-import com.android.gallery3d.filtershow.ui.SliderListener;
 
 import java.io.File;
 
 public class ImageShow extends View implements OnGestureListener,
         ScaleGestureDetector.OnScaleGestureListener,
-        OnDoubleTapListener,
-        SliderListener,
-        OnSeekBarChangeListener {
+        OnDoubleTapListener {
 
     private static final String LOGTAG = "ImageShow";
 
@@ -61,7 +57,7 @@
     private ScaleGestureDetector mScaleGestureDetector = null;
 
     protected Rect mImageBounds = new Rect();
-
+    private boolean mOriginalDisabled = false;
     private boolean mTouchShowOriginal = false;
     private long mTouchShowOriginalDate = 0;
     private final long mTouchShowOriginalDelayMin = 200; // 200ms
@@ -82,16 +78,10 @@
         return new GeometryMetadata(getImagePreset().mGeoData);
     }
 
-    public void setGeometry(GeometryMetadata d) {
-        getImagePreset().mGeoData.set(d);
-    }
-
-    private boolean mShowControls = false;
     private String mToast = null;
     private boolean mShowToast = false;
     private boolean mImportantToast = false;
 
-    private SeekBar mSeekBar = null;
     private PanelController mController = null;
 
     private FilterShowActivity mActivity = null;
@@ -131,30 +121,9 @@
     private final Handler mHandler = new Handler();
 
     public void select() {
-        if (mSeekBar != null) {
-            mSeekBar.setOnSeekBarChangeListener(this);
-        }
-    }
-
-    private int parameterToUI(int parameter, int minp, int maxp, int uimax) {
-        return (uimax * (parameter - minp)) / (maxp - minp);
-    }
-
-    private int uiToParameter(int ui, int minp, int maxp, int uimax) {
-        return ((maxp - minp) * ui) / uimax + minp;
-    }
-
-    public void updateSeekBar(int parameter, int minp, int maxp) {
-        if (mSeekBar == null) {
-            return;
-        }
-        int seekMax = mSeekBar.getMax();
-        int progress = parameterToUI(parameter, minp, maxp, seekMax);
-        mSeekBar.setProgress(progress);
     }
 
     public void unselect() {
-
     }
 
     public boolean hasModifications() {
@@ -176,7 +145,6 @@
         return mController;
     }
 
-    @Override
     public void onNewValue(int parameter) {
         if (getImagePreset() != null) {
             getImagePreset().fillImageStateAdapter(MasterImage.getImage().getState());
@@ -192,17 +160,6 @@
         return mTouch;
     }
 
-    @Override
-    public void onTouchDown(float x, float y) {
-        mTouch.x = (int) x;
-        mTouch.y = (int) y;
-        invalidate();
-    }
-
-    @Override
-    public void onTouchUp() {
-    }
-
     public ImageShow(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -231,10 +188,6 @@
         setMeasuredDimension(parentWidth, parentHeight);
     }
 
-    public void setSeekBar(SeekBar seekBar) {
-        mSeekBar = seekBar;
-    }
-
     public ImageFilter getCurrentFilter() {
         return MasterImage.getImage().getCurrentFilter();
     }
@@ -277,6 +230,10 @@
      */
     protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
         GeometryMetadata geo = getImagePreset().mGeoData;
+        if (geo == null || mImageLoader == null
+                || mImageLoader.getOriginalBounds() == null) {
+            return new Matrix();
+        }
         Matrix m = geo.getOriginalToScreen(reflectRotation,
                 mImageLoader.getOriginalBounds().width(),
                 mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
@@ -339,7 +296,7 @@
 
     @Override
     public void onDraw(Canvas canvas) {
-
+        MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
         canvas.save();
         // TODO: center scale on gesture
         float cx = canvas.getWidth()/2.0f;
@@ -363,6 +320,12 @@
                     1.5f * mTextPadding, mPaint);
         }
 
+        Bitmap partialPreview = MasterImage.getImage().getPartialImage();
+        if (partialPreview != null) {
+            Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
+            Rect dest = new Rect(0, 0, getWidth(), getHeight());
+            canvas.drawBitmap(partialPreview, src, dest, mPaint);
+        }
         drawToast(canvas);
     }
 
@@ -476,28 +439,6 @@
         }
     }
 
-    public ImageShow setShowControls(boolean value) {
-        mShowControls = value;
-        if (mShowControls) {
-            if (mSeekBar != null) {
-                mSeekBar.setVisibility(View.VISIBLE);
-            }
-        } else {
-            if (mSeekBar != null) {
-                mSeekBar.setVisibility(View.INVISIBLE);
-            }
-        }
-        return this;
-    }
-
-    public boolean showControls() {
-        return mShowControls;
-    }
-
-    public boolean showHires() {
-        return true;
-    }
-
     public boolean showTitle() {
         return false;
     }
@@ -574,6 +515,14 @@
         return mScaleGestureDetector.isInProgress();
     }
 
+    protected boolean isOriginalDisabled() {
+        return mOriginalDisabled;
+    }
+
+    protected void setOriginalDisabled(boolean originalDisabled) {
+        mOriginalDisabled = originalDisabled;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         super.onTouchEvent(event);
@@ -609,8 +558,9 @@
                     Point translation = MasterImage.getImage().getTranslation();
                     translation.x = (int) (originalTranslation.x + translateX);
                     translation.y = (int) (originalTranslation.y + translateY);
+                    MasterImage.getImage().setTranslation(translation);
                 }
-            } else if (!mActivity.isShowingHistoryPanel()
+            } else if (!mOriginalDisabled && !mActivity.isShowingHistoryPanel()
                     && (System.currentTimeMillis() - mTouchShowOriginalDate
                             > mTouchShowOriginalDelayMin)
                     && event.getPointerCount() == 1) {
@@ -638,48 +588,6 @@
         invalidate();
     }
 
-    public float getImageRotation() {
-        return getImagePreset().mGeoData.getRotation();
-    }
-
-    public float getImageRotationZoomFactor() {
-        return getImagePreset().mGeoData.getScaleFactor();
-    }
-
-    public void setImageRotation(float r) {
-        getImagePreset().mGeoData.setRotation(r);
-    }
-
-    public void setImageRotationZoomFactor(float f) {
-        getImagePreset().mGeoData.setScaleFactor(f);
-    }
-
-    public void setImageRotation(float imageRotation,
-            float imageRotationZoomFactor) {
-        float r = getImageRotation();
-        if (imageRotation != r) {
-            invalidate();
-        }
-        setImageRotation(imageRotation);
-        setImageRotationZoomFactor(imageRotationZoomFactor);
-    }
-
-    @Override
-    public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
-        int parameter = progress;
-        onNewValue(parameter);
-    }
-
-    @Override
-    public void onStartTrackingTouch(SeekBar arg0) {
-        // TODO Auto-generated method stub
-    }
-
-    @Override
-    public void onStopTrackingTouch(SeekBar arg0) {
-        // TODO Auto-generated method stub
-    }
-
     @Override
     public boolean onDoubleTap(MotionEvent arg0) {
         // TODO Auto-generated method stub
@@ -706,6 +614,9 @@
 
     @Override
     public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
+        if (mActivity == null) {
+            return false;
+        }
         if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX())
                 || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) {
             if (!mTouchShowOriginal
@@ -783,4 +694,5 @@
         }
         return false;
     }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
index 729ac7f..a51d102 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.gallery3d.filtershow.editors.EditorVignette;
@@ -48,16 +49,28 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         int mask = event.getActionMasked();
-        if (MotionEvent.ACTION_UP == mask) {
-            mActiveHandle = -1;
-        }
-        if (MotionEvent.ACTION_DOWN == mask && event.getPointerCount() == 1) {
-            mActiveHandle = mElipse.getCloseHandle(event.getX(), event.getY());
-        }
-        if (mActiveHandle == -1 || event.getPointerCount() > 1) {
-            mActiveHandle = -1;
-
-            return super.onTouchEvent(event);
+        if (mActiveHandle == -1) {
+            if (MotionEvent.ACTION_DOWN != mask) {
+                return super.onTouchEvent(event);
+            }
+            if (event.getPointerCount() == 1) {
+                mActiveHandle = mElipse.getCloseHandle(event.getX(), event.getY());
+            }
+            if (mActiveHandle == -1) {
+                return super.onTouchEvent(event);
+            }
+        } else {
+            switch (mask) {
+                case MotionEvent.ACTION_UP:
+                    mActiveHandle = -1;
+                    break;
+                case MotionEvent.ACTION_DOWN:
+                    if (event.getPointerCount() == 1) {
+                        Log.v(LOGTAG, "################### ACTION_DOWN odd " + mActiveHandle
+                                + " touches=1");
+                    }
+                    break;
+            }
         }
         float x = event.getX();
         float y = event.getY();
@@ -121,9 +134,7 @@
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mElipse.isUndefined()) {
-            setRepresentation(mVignetteRep);
-        }
+        setRepresentation(mVignetteRep);
         mElipse.draw(canvas);
 
     }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
index 263a5b5..eb568c3 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
@@ -49,9 +49,7 @@
         mTouchDown = false;
     }
 
-    @Override
     public void onTouchDown(float x, float y) {
-        super.onTouchDown(x, y);
         if (mZoomedIn || mTouchDown) {
             return;
         }
@@ -81,7 +79,6 @@
         invalidate();
     }
 
-    @Override
     public void onTouchUp() {
         mTouchDown = false;
     }
@@ -93,7 +90,7 @@
         Bitmap filteredImage = null;
         if ((mZoomedIn || mTouchDown) && mImageLoader != null) {
             filteredImage = mImageLoader.getScaleOneImageForPreset(this, getImagePreset(),
-                    mZoomBounds, false);
+                    mZoomBounds, null, false);
         } else {
             filteredImage = getFilteredImage();
         }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
index 905d2c3..9eafe22 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -16,9 +16,7 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.RectF;
+import android.graphics.*;
 
 import com.android.gallery3d.filtershow.FilterShowActivity;
 import com.android.gallery3d.filtershow.HistoryAdapter;
@@ -45,6 +43,7 @@
 
     private Bitmap mGeometryOnlyBitmap = null;
     private Bitmap mFiltersOnlyBitmap = null;
+    private Bitmap mPartialBitmap = null;
 
     private ImageLoader mLoader = null;
     private HistoryAdapter mHistory = null;
@@ -62,6 +61,8 @@
     private Point mTranslation = new Point();
     private Point mOriginalTranslation = new Point();
 
+    private Point mImageShowSize = new Point();
+
     private MasterImage() {
     }
 
@@ -193,6 +194,10 @@
         return mGeometryOnlyBitmap;
     }
 
+    public Bitmap getPartialImage() {
+        return mPartialBitmap;
+    }
+
     public void notifyObservers() {
         for (ImageShow observer : mObservers) {
             observer.invalidate();
@@ -221,6 +226,7 @@
             }
         }
         invalidatePreview();
+        needsUpdateFullResPreview();
         mActivity.enableSave(hasModifications());
     }
 
@@ -237,11 +243,67 @@
         updatePresets(false);
     }
 
+    public void invalidatePartialPreview() {
+        if (mPartialBitmap != null) {
+            mPartialBitmap = null;
+            notifyObservers();
+        }
+    }
+
     public void invalidatePreview() {
         mFilteredPreview.invalidate();
+        invalidatePartialPreview();
+        needsUpdateFullResPreview();
         FilteringPipeline.getPipeline().updatePreviewBuffer();
     }
 
+    public void setImageShowSize(int w, int h) {
+        if (mImageShowSize.x != w || mImageShowSize.y != h) {
+            mImageShowSize.set(w, h);
+            needsUpdateFullResPreview();
+        }
+    }
+
+    private Matrix getImageToScreenMatrix(boolean reflectRotation) {
+        GeometryMetadata geo = mPreset.mGeoData;
+        if (geo == null || mLoader == null
+                || mLoader.getOriginalBounds() == null
+                || mImageShowSize.x == 0) {
+            return new Matrix();
+        }
+        Matrix m = geo.getOriginalToScreen(reflectRotation,
+                mLoader.getOriginalBounds().width(),
+                mLoader.getOriginalBounds().height(), mImageShowSize.x, mImageShowSize.y);
+        Point translate = getTranslation();
+        float scaleFactor = getScaleFactor();
+        m.postTranslate(translate.x, translate.y);
+        m.postScale(scaleFactor, scaleFactor, mImageShowSize.x/2.0f, mImageShowSize.y/2.0f);
+        return m;
+    }
+
+    private Matrix getScreenToImageMatrix(boolean reflectRotation) {
+        Matrix m = getImageToScreenMatrix(reflectRotation);
+        Matrix invert = new Matrix();
+        m.invert(invert);
+        return invert;
+    }
+
+    public void needsUpdateFullResPreview() {
+        if (!mPreset.canDoPartialRendering()) {
+            invalidatePartialPreview();
+            return;
+        }
+        Matrix m = getScreenToImageMatrix(true);
+        RectF r = new RectF(0, 0, mImageShowSize.x, mImageShowSize.y);
+        RectF dest = new RectF();
+        m.mapRect(dest, r);
+        Rect bounds = new Rect();
+        dest.roundOut(bounds);
+        RenderingRequest.post(null, mPreset, RenderingRequest.PARTIAL_RENDERING,
+                this, bounds, new Rect(0, 0, mImageShowSize.x, mImageShowSize.y));
+        invalidatePartialPreview();
+    }
+
     @Override
     public void available(RenderingRequest request) {
         if (request.getBitmap() == null) {
@@ -253,6 +315,10 @@
         if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
             mFiltersOnlyBitmap = request.getBitmap();
         }
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            mPartialBitmap = request.getBitmap();
+            notifyObservers();
+        }
     }
 
     public static void reset() {
@@ -275,6 +341,7 @@
 
     public void setScaleFactor(float scaleFactor) {
         mScaleFactor = scaleFactor;
+        needsUpdateFullResPreview();
     }
 
     public Point getTranslation() {
@@ -282,7 +349,9 @@
     }
 
     public void setTranslation(Point translation) {
-        mTranslation = translation;
+        mTranslation.x = translation.x;
+        mTranslation.y = translation.y;
+        needsUpdateFullResPreview();
     }
 
     public Point getOriginalTranslation() {
@@ -297,5 +366,6 @@
     public void resetTranslation() {
         mTranslation.x = 0;
         mTranslation.y = 0;
+        needsUpdateFullResPreview();
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index 84266c5..ae5a034 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -17,6 +17,7 @@
 package com.android.gallery3d.filtershow.presets;
 
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.util.Log;
 
 import com.android.gallery3d.filtershow.ImageStateAdapter;
@@ -52,6 +53,8 @@
     private boolean mDoApplyFilters = true;
 
     public final GeometryMetadata mGeoData = new GeometryMetadata();
+    private boolean mPartialRendering = false;
+    private Rect mPartialRenderingBounds;
 
     public ImagePreset() {
         setup();
@@ -421,6 +424,22 @@
         return bitmap;
     }
 
+    public boolean canDoPartialRendering() {
+        if (mGeoData.hasModifications()) {
+            return false;
+        }
+        for (int i = 0; i < mFilters.size(); i++) {
+            FilterRepresentation representation = null;
+            synchronized (mFilters) {
+                representation = mFilters.elementAt(i);
+            }
+            if (!representation.supportsPartialRendering()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public void fillImageStateAdapter(ImageStateAdapter imageStateAdapter) {
         if (imageStateAdapter == null) {
             return;
@@ -446,4 +465,17 @@
     public void setScaleFactor(float value) {
         mScaleFactor = value;
     }
+
+    public void setPartialRendering(boolean partialRendering, Rect bounds) {
+        mPartialRendering = partialRendering;
+        mPartialRenderingBounds = bounds;
+    }
+
+    public boolean isPartialRendering() {
+        return mPartialRendering;
+    }
+
+    public Rect getPartialRenderingBounds() {
+        return mPartialRenderingBounds;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index e378fe2..89cfa6bd 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -22,6 +22,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Environment;
@@ -173,37 +174,52 @@
         }
         ImagePreset preset = params[0];
         InputStream is = null;
-        try {
-            Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri);
-            if (bitmap == null) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        boolean noBitmap = true;
+        int num_tries = 0;
+        // Stopgap fix for low-memory devices.
+        while(noBitmap) {
+            try {
+                // Try to do bitmap operations, downsample if low-memory
+                Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options);
+                if (bitmap == null) {
+                    return null;
+                }
+                bitmap = preset.applyGeometry(bitmap);
+                bitmap = preset.apply(bitmap);
+
+                Object xmp = null;
+                if (preset.isPanoramaSafe()) {
+                    is = context.getContentResolver().openInputStream(sourceUri);
+                    xmp =  XmpUtilHelper.extractXMPMeta(is);
+                }
+                ExifData exif = getExifData(sourceUri);
+                if (exif != null) {
+                    exif.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, System.currentTimeMillis(),
+                            TimeZone.getDefault());
+                    // Since the image has been modified, set the orientation to normal.
+                    exif.addTag(ExifTag.TAG_ORIENTATION).setValue(ExifTag.Orientation.TOP_LEFT);
+                }
+                saveBitmap(bitmap, this.destinationFile, xmp, exif);
+                bitmap.recycle();
+                noBitmap = false;
+            } catch (FileNotFoundException ex) {
+                Log.w(LOGTAG, "Failed to save image!", ex);
                 return null;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= 5) {
+                    throw e;
+                }
+                System.gc();
+                options.inSampleSize *= 2;
+            } finally {
+                Utils.closeSilently(is);
             }
-            bitmap = preset.applyGeometry(bitmap);
-            bitmap = preset.apply(bitmap);
-
-            Object xmp = null;
-            if (preset.isPanoramaSafe()) {
-                is = context.getContentResolver().openInputStream(sourceUri);
-                xmp =  XmpUtilHelper.extractXMPMeta(is);
-            }
-            ExifData exif = getExifData(sourceUri);
-            if (exif != null) {
-                exif.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, System.currentTimeMillis(),
-                        TimeZone.getDefault());
-                // Since the image has been modified, set the orientation to normal.
-                exif.addTag(ExifTag.TAG_ORIENTATION).setValue(ExifTag.Orientation.TOP_LEFT);
-            }
-            saveBitmap(bitmap, this.destinationFile, xmp, exif);
-
-            Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
-            bitmap.recycle();
-            return uri;
-        } catch (FileNotFoundException ex) {
-            Log.w(LOGTAG, "Failed to save image!", ex);
-            return null;
-        } finally {
-            Utils.closeSilently(is);
         }
+        Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
+        return uri;
+
     }
 
     @Override
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderController.java b/src/com/android/gallery3d/filtershow/ui/SliderController.java
deleted file mode 100644
index 7139ace..0000000
--- a/src/com/android/gallery3d/filtershow/ui/SliderController.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.filtershow.ui;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.view.MotionEvent;
-
-public class SliderController {
-    private static final String LOGTAG = "SliderController";
-
-    private float mCenterX;
-    private float mCenterY;
-    private float mCurrentX;
-    private float mCurrentY;
-    private int mValue = 100;
-    int mOriginalValue = 0;
-
-    private int mWidth = 0;
-    private int mHeight = 0;
-
-    private String mToast = null;
-
-    private final Paint mPaint = new Paint();
-
-    private SliderListener mListener = null;
-
-    private MODES mMode = MODES.NONE;
-    private static int mTextSize = 128;
-
-    private enum MODES {
-        NONE, DOWN, UP, MOVE
-    }
-
-    public void onDraw(Canvas canvas) {
-        if (mMode == MODES.NONE || mMode == MODES.UP) {
-            return;
-        }
-    }
-
-    public void drawToast(Canvas canvas) {
-        if (mToast != null) {
-            canvas.save();
-            mPaint.setTextSize(mTextSize);
-            float textWidth = mPaint.measureText(mToast);
-            int toastX = (int) ((getWidth() - textWidth) / 2.0f);
-            int toastY = (int) (getHeight() / 3.0f);
-
-            mPaint.setARGB(255, 0, 0, 0);
-            canvas.drawText(mToast, toastX - 2, toastY - 2, mPaint);
-            canvas.drawText(mToast, toastX - 2, toastY, mPaint);
-            canvas.drawText(mToast, toastX, toastY - 2, mPaint);
-            canvas.drawText(mToast, toastX + 2, toastY + 2, mPaint);
-            canvas.drawText(mToast, toastX + 2, toastY, mPaint);
-            canvas.drawText(mToast, toastX, toastY + 2, mPaint);
-            mPaint.setARGB(255, 255, 255, 255);
-            canvas.drawText(mToast, toastX, toastY, mPaint);
-            canvas.restore();
-        }
-    }
-
-    protected int computeValue() {
-        int delta = (int) (100 * (getCurrentX() - getCenterX()) / getWidth());
-        int value = mOriginalValue + delta;
-        if (value < -100) {
-            value = -100;
-        } else if (value > 100) {
-            value = 100;
-        }
-        setValue(value);
-        mToast = "" + value;
-        return value;
-    }
-
-    public void setValue(int value) {
-        mValue = value;
-    }
-
-    public int getWidth() {
-        return mWidth;
-    }
-
-    public int getHeight() {
-        return mHeight;
-    }
-
-    public void setWidth(int value) {
-        mWidth = value;
-    }
-
-    public void setHeight(int value) {
-        mHeight = value;
-    }
-
-    public float getCurrentX() {
-        return mCurrentX;
-    }
-
-    public float getCurrentY() {
-        return mCurrentY;
-    }
-
-    public float getCenterX() {
-        return mCenterX;
-    }
-
-    public float getCenterY() {
-        return mCenterY;
-    }
-
-    public void setActionDown(float x, float y) {
-        mCenterX = x;
-        mCenterY = y;
-        mCurrentX = x;
-        mCurrentY = y;
-        mMode = MODES.DOWN;
-        if (mListener != null) {
-            mListener.onTouchDown(x, y);
-        }
-    }
-
-    public void setActionMove(float x, float y) {
-        mCurrentX = x;
-        mCurrentY = y;
-        mMode = MODES.MOVE;
-        computeValue();
-        if (mListener != null) {
-            mListener.onNewValue(mValue);
-        }
-    }
-
-    public void setActionUp() {
-        mMode = MODES.UP;
-        mOriginalValue = computeValue();
-        if (mListener != null) {
-            mListener.onTouchUp();
-        }
-    }
-
-    public void setNoAction() {
-        mMode = MODES.NONE;
-    }
-
-    public void setListener(SliderListener listener) {
-        mListener = listener;
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        setNoAction();
-        switch (event.getActionMasked()) {
-            case (MotionEvent.ACTION_DOWN): {
-                setActionDown(event.getX(), event.getY());
-                break;
-            }
-            case (MotionEvent.ACTION_UP): {
-                setActionUp();
-                break;
-            }
-            case (MotionEvent.ACTION_CANCEL): {
-                setActionUp();
-                break;
-            }
-            case (MotionEvent.ACTION_MOVE): {
-                setActionMove(event.getX(), event.getY());
-                break;
-            }
-        }
-        return true;
-    }
-
-    public void reset() {
-        mOriginalValue = 0;
-    }
-
-}
diff --git a/src/com/android/gallery3d/ingest/IngestService.java b/src/com/android/gallery3d/ingest/IngestService.java
index 5e0ca0b..fa421e7 100644
--- a/src/com/android/gallery3d/ingest/IngestService.java
+++ b/src/com/android/gallery3d/ingest/IngestService.java
@@ -187,7 +187,6 @@
     public void deviceRemoved(MtpDevice device) {
         if (device == mDevice) {
             setDevice(null);
-            MtpBitmapFetch.onDeviceDisconnected(device);
         }
     }
 
diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
index 46a2051..5e1fb34 100644
--- a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
+++ b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
@@ -25,19 +25,14 @@
 import android.view.WindowManager;
 
 import com.android.camera.Exif;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
-
-import java.util.ArrayList;
+import com.android.photos.data.GalleryBitmapPool;
 
 public class MtpBitmapFetch {
-    private static final int BITMAP_POOL_SIZE = 32;
-    private static BitmapPool sThumbnailPool = new BitmapPool(BITMAP_POOL_SIZE);
     private static int sMaxSize = 0;
 
     public static void recycleThumbnail(Bitmap b) {
         if (b != null) {
-            sThumbnailPool.recycle(b);
+            GalleryBitmapPool.getInstance().put(b);
         }
     }
 
@@ -48,7 +43,7 @@
         o.inJustDecodeBounds = true;
         BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
         if (o.outWidth == 0 || o.outHeight == 0) return null;
-        o.inBitmap = sThumbnailPool.getBitmap(o.outWidth, o.outHeight);
+        o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
         o.inMutable = true;
         o.inJustDecodeBounds = false;
         o.inSampleSize = 1;
@@ -86,10 +81,6 @@
         return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
     }
 
-    public static void onDeviceDisconnected(MtpDevice device) {
-        sThumbnailPool.clear();
-    }
-
     public static void configureForContext(Context context) {
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
index 6eeeec0..da1cac0 100644
--- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java
+++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
@@ -27,8 +27,8 @@
 import android.text.TextUtils;
 
 import com.android.gallery3d.R;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
@@ -41,7 +41,8 @@
     private final Context mContext;
 
     private int mLabelWidth;
-    private BitmapPool mBitmapPool;
+    private int mBitmapWidth;
+    private int mBitmapHeight;
 
     private final LazyLoadedBitmap mLocalSetIcon;
     private final LazyLoadedBitmap mPicasaIcon;
@@ -109,8 +110,8 @@
         if (mLabelWidth == width) return;
         mLabelWidth = width;
         int borders = 2 * BORDER_SIZE;
-        mBitmapPool = new BitmapPool(
-                width + borders, mSpec.labelBackgroundHeight + borders, 16);
+        mBitmapWidth = width + borders;
+        mBitmapHeight = mSpec.labelBackgroundHeight + borders;
     }
 
     public ThreadPool.Job<Bitmap> requestLabel(
@@ -152,7 +153,7 @@
 
             synchronized (this) {
                 labelWidth = mLabelWidth;
-                bitmap = mBitmapPool.getBitmap();
+                bitmap = GalleryBitmapPool.getInstance().get(mBitmapWidth, mBitmapHeight);
             }
 
             if (bitmap == null) {
@@ -200,10 +201,6 @@
     }
 
     public void recycleLabel(Bitmap label) {
-        mBitmapPool.recycle(label);
-    }
-
-    public void clearRecycledLabels() {
-        if (mBitmapPool != null) mBitmapPool.clear();
+        GalleryBitmapPool.getInstance().put(label);
     }
 }
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index d5a15b4..8149df4 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -23,7 +23,6 @@
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumSetDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
@@ -403,7 +402,6 @@
         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
             freeSlotContent(i);
         }
-        mLabelMaker.clearRecycledLabels();
     }
 
     public void resume() {
@@ -429,12 +427,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(mMediaItem.requestImage(
                     MediaItem.TYPE_MICROTHUMBNAIL), l);
@@ -505,11 +497,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            mLabelMaker.recycleLabel(bitmap);
-        }
-
-        @Override
         protected void onLoadComplete(Bitmap bitmap) {
             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
         }
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index 8cd2cf5..fec7d1e 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -22,11 +22,10 @@
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
+import com.android.gallery3d.data.Path;
 import com.android.gallery3d.glrenderer.Texture;
 import com.android.gallery3d.glrenderer.TiledTexture;
 import com.android.gallery3d.util.Future;
@@ -296,12 +295,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(
                     mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
diff --git a/src/com/android/gallery3d/ui/BitmapLoader.java b/src/com/android/gallery3d/ui/BitmapLoader.java
index 4f07cc0..a708a90 100644
--- a/src/com/android/gallery3d/ui/BitmapLoader.java
+++ b/src/com/android/gallery3d/ui/BitmapLoader.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 
@@ -51,7 +52,7 @@
             mBitmap = future.get();
             if (mState == STATE_RECYCLED) {
                 if (mBitmap != null) {
-                    recycleBitmap(mBitmap);
+                    GalleryBitmapPool.getInstance().put(mBitmap);
                     mBitmap = null;
                 }
                 return; // don't call callback
@@ -84,7 +85,7 @@
     public synchronized void recycle() {
         mState = STATE_RECYCLED;
         if (mBitmap != null) {
-            recycleBitmap(mBitmap);
+            GalleryBitmapPool.getInstance().put(mBitmap);
             mBitmap = null;
         }
         if (mTask != null) mTask.cancel();
@@ -103,6 +104,5 @@
     }
 
     abstract protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l);
-    abstract protected void recycleBitmap(Bitmap bitmap);
     abstract protected void onLoadComplete(Bitmap bitmap);
 }
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index c3466e7..e1a8b76 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -21,7 +21,7 @@
 import android.graphics.Canvas;
 
 import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 import java.util.ArrayList;
 
@@ -71,12 +71,11 @@
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize,
-            BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         x >>= level;
         y >>= level;
 
-        Bitmap result = pool == null ? null : pool.getBitmap();
+        Bitmap result = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (result == null) {
             result = Bitmap.createBitmap(tileSize, tileSize, mConfig);
         } else {
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index f1c31e4..3185c75 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -27,10 +27,9 @@
 import android.view.WindowManager;
 
 import com.android.gallery3d.app.GalleryContext;
-import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DecodeUtils;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.glrenderer.UploadedTexture;
 import com.android.gallery3d.util.Future;
@@ -50,8 +49,6 @@
     // TILE_SIZE must be 2^N
     private static int sTileSize;
 
-    private static BitmapPool sTilePool;
-
     /*
      *  This is the tile state in the CPU side.
      *  Life of a Tile:
@@ -143,8 +140,7 @@
         // still refers to the coordinate on the original image.
         //
         // The method would be called in another thread.
-        public Bitmap getTile(int level, int x, int y, int tileSize,
-                BitmapPool pool);
+        public Bitmap getTile(int level, int x, int y, int tileSize);
     }
 
     public static boolean isHighResolution(Context context) {
@@ -164,10 +160,6 @@
             } else {
                 sTileSize = 256;
             }
-            sTilePool =
-                    ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
-                    ? new BitmapPool(sTileSize, sTileSize, 128)
-                    : null;
         }
     }
 
@@ -399,7 +391,6 @@
             }
         }
         setScreenNail(null);
-        if (sTilePool != null) sTilePool.clear();
     }
 
     public void prepareTextures() {
@@ -508,7 +499,7 @@
             if (tile.mTileState == STATE_RECYCLING) {
                 tile.mTileState = STATE_RECYCLED;
                 if (tile.mDecodedTile != null) {
-                    if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+                    GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
                     tile.mDecodedTile = null;
                 }
                 mRecycledQueue.push(tile);
@@ -536,7 +527,7 @@
         }
         tile.mTileState = STATE_RECYCLED;
         if (tile.mDecodedTile != null) {
-            if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+            GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
             tile.mDecodedTile = null;
         }
         mRecycledQueue.push(tile);
@@ -675,7 +666,7 @@
 
         @Override
         protected void onFreeBitmap(Bitmap bitmap) {
-            if (sTilePool != null) sTilePool.recycle(bitmap);
+            GalleryBitmapPool.getInstance().put(bitmap);
         }
 
         boolean decode() {
@@ -683,7 +674,7 @@
             // by (1 << mTilelevel) from a region in the original image.
             try {
                 mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(
-                        mTileLevel, mX, mY, sTileSize, sTilePool));
+                        mTileLevel, mX, mY, sTileSize));
             } catch (Throwable t) {
                 Log.w(TAG, "fail to decode tile", t);
             }
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 0d20b07..0c1f66d 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -26,7 +26,7 @@
 
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 public class TileImageViewAdapter implements TileImageView.TileSource {
     private static final String TAG = "TileImageViewAdapter";
@@ -84,7 +84,7 @@
     // (44, 44, 256, 256) from the original photo and down sample it to 106.
     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
             return getTileWithoutReusingBitmap(level, x, y, tileSize);
         }
@@ -106,7 +106,7 @@
                     .contains(wantRegion);
         }
 
-        Bitmap bitmap = pool == null ? null : pool.getBitmap();
+        Bitmap bitmap = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (bitmap != null) {
             if (needClear) bitmap.eraseColor(0);
         } else {
@@ -126,7 +126,7 @@
             }
         } finally {
             if (options.inBitmap != bitmap && options.inBitmap != null) {
-                if (pool != null) pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
         }
diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java
index ab24f5b..860e230 100644
--- a/src/com/android/gallery3d/ui/TiledScreenNail.java
+++ b/src/com/android/gallery3d/ui/TiledScreenNail.java
@@ -20,8 +20,7 @@
 import android.graphics.RectF;
 
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
-import com.android.gallery3d.data.MediaItem;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.glrenderer.TiledTexture;
 
@@ -83,11 +82,6 @@
         mHeight = Math.round(scale * height);
     }
 
-    private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) {
-        if (pool == null || bitmap == null) return;
-        pool.recycle(bitmap);
-    }
-
     // Combines the two ScreenNails.
     // Returns the used one and recycle the unused one.
     public ScreenNail combine(ScreenNail other) {
@@ -106,7 +100,7 @@
         mWidth = newer.mWidth;
         mHeight = newer.mHeight;
         if (newer.mTexture != null) {
-            recycleBitmap(MediaItem.getThumbPool(), mBitmap);
+            if (mBitmap != null) GalleryBitmapPool.getInstance().put(mBitmap);
             if (mTexture != null) mTexture.recycle();
             mBitmap = newer.mBitmap;
             mTexture = newer.mTexture;
@@ -143,8 +137,10 @@
             mTexture.recycle();
             mTexture = null;
         }
-        recycleBitmap(MediaItem.getThumbPool(), mBitmap);
-        mBitmap = null;
+        if (mBitmap != null) {
+            GalleryBitmapPool.getInstance().put(mBitmap);
+            mBitmap = null;
+        }
     }
 
     public static void disableDrawPlaceholder() {
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderListener.java b/src/com/android/photos/AlbumSetFragment.java
similarity index 69%
rename from src/com/android/gallery3d/filtershow/ui/SliderListener.java
rename to src/com/android/photos/AlbumSetFragment.java
index 6d4718d..2d348c2 100644
--- a/src/com/android/gallery3d/filtershow/ui/SliderListener.java
+++ b/src/com/android/photos/AlbumSetFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.gallery3d.filtershow.ui;
+package com.android.photos;
 
-public interface SliderListener {
-    public void onNewValue(int value);
-    public void onTouchDown(float x, float y);
-    public void onTouchUp();
+import android.app.Fragment;
+
+
+public class AlbumSetFragment extends Fragment {
+
 }
diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java
new file mode 100644
index 0000000..46b5140
--- /dev/null
+++ b/src/com/android/photos/GalleryActivity.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+public class GalleryActivity extends Activity {
+
+    private final String FTAG_PHOTOSET = "PhotoSet";
+    private final String FTAG_ALBUMSET = "AlbumSet";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setupActionBar();
+    }
+
+    private void setupActionBar() {
+        ActionBar ab = getActionBar();
+        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        ab.setDisplayShowHomeEnabled(false);
+        ab.setDisplayShowTitleEnabled(false);
+        Tab tab = ab.newTab();
+        tab.setText(R.string.tab_photos);
+        tab.setTabListener(new TabListener<PhotoSetFragment>(this,
+                FTAG_PHOTOSET, PhotoSetFragment.class));
+        ab.addTab(tab, true);
+        tab = ab.newTab();
+        tab.setText(R.string.tab_albums);
+        tab.setTabListener(new TabListener<AlbumSetFragment>(this,
+                FTAG_ALBUMSET, AlbumSetFragment.class));
+        ab.addTab(tab);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.gallery, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case R.id.menu_camera:
+            Intent intent = new Intent(this, CameraActivity.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    private static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+        private Fragment mFragment;
+        private final Activity mActivity;
+        private final String mTag;
+        private final Class<T> mClass;
+
+        /** Constructor used each time a new tab is created.
+          * @param activity  The host Activity, used to instantiate the fragment
+          * @param tag  The identifier tag for the fragment
+          * @param clz  The fragment's Class, used to instantiate the fragment
+          */
+        public TabListener(Activity activity, String tag, Class<T> clz) {
+            mActivity = activity;
+            mTag = tag;
+            mClass = clz;
+        }
+
+        /* The following are each of the ActionBar.TabListener callbacks */
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            // Check if the fragment is already initialized
+            if (mFragment == null) {
+                // If not, instantiate and add it to the activity
+                mFragment = Fragment.instantiate(mActivity, mClass.getName());
+                ft.add(android.R.id.content, mFragment, mTag);
+            } else {
+                // If it exists, simply attach it in order to show it
+                ft.attach(mFragment);
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            if (mFragment != null) {
+                // Detach the fragment, because another one is being attached
+                ft.detach(mFragment);
+            }
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+            // User selected the already selected tab. Usually do nothing.
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderListener.java b/src/com/android/photos/PhotoFragment.java
similarity index 69%
copy from src/com/android/gallery3d/filtershow/ui/SliderListener.java
copy to src/com/android/photos/PhotoFragment.java
index 6d4718d..3be6313 100644
--- a/src/com/android/gallery3d/filtershow/ui/SliderListener.java
+++ b/src/com/android/photos/PhotoFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.gallery3d.filtershow.ui;
 
-public interface SliderListener {
-    public void onNewValue(int value);
-    public void onTouchDown(float x, float y);
-    public void onTouchUp();
+package com.android.photos;
+
+import android.app.Fragment;
+
+
+public class PhotoFragment extends Fragment {
+
 }
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
new file mode 100644
index 0000000..e9bfce5
--- /dev/null
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.MediaStore.Files.FileColumns;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.PhotoSetLoader;
+
+
+public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor> {
+
+    private static final int LOADER_PHOTOSET = 1;
+
+    private ListView mPhotoSetView;
+    private View mEmptyView;
+    private CursorAdapter mAdapter;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.photo_set, container, false);
+        mPhotoSetView = (ListView) root.findViewById(android.R.id.list);
+        mEmptyView = root.findViewById(android.R.id.empty);
+        mEmptyView.setVisibility(View.GONE);
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_1, null,
+                new String[] { FileColumns.DATA },
+                new int[] { android.R.id.text1 }, 0);
+        mPhotoSetView.setAdapter(mAdapter);
+        getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
+        updateEmptyStatus();
+        return root;
+    }
+
+    private void updateEmptyStatus() {
+        boolean empty = (mAdapter == null || mAdapter.getCount() == 0);
+        mPhotoSetView.setVisibility(empty ? View.GONE : View.VISIBLE);
+        mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        return new PhotoSetLoader(getActivity());
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader,
+            Cursor data) {
+        mAdapter.swapCursor(data);
+        updateEmptyStatus();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+}
diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java
new file mode 100644
index 0000000..cddc160
--- /dev/null
+++ b/src/com/android/photos/data/GalleryBitmapPool.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos.data;
+
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+
+import com.android.photos.data.SparseArrayBitmapPool.Node;
+
+public class GalleryBitmapPool {
+
+    private static final int CAPACITY_BYTES = 20971520;
+    private static final int POOL_INDEX_NONE = -1;
+    private static final int POOL_INDEX_SQUARE = 0;
+    private static final int POOL_INDEX_PHOTO = 1;
+    private static final int POOL_INDEX_MISC = 2;
+
+    private static final Point[] COMMON_PHOTO_ASPECT_RATIOS =
+        { new Point(4, 3), new Point(3, 2), new Point(16, 9) };
+
+    private int mCapacityBytes;
+    private SparseArrayBitmapPool [] mPools;
+    private Pool<Node> mSharedNodePool = new SimplePool<Node>(128);
+
+    private GalleryBitmapPool(int capacityBytes) {
+        mPools = new SparseArrayBitmapPool[3];
+        mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mCapacityBytes = capacityBytes;
+    }
+
+    private static GalleryBitmapPool sInstance;
+    public static GalleryBitmapPool getInstance() {
+        if (sInstance == null) {
+            sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
+        }
+        return sInstance;
+    }
+
+    private SparseArrayBitmapPool getPoolForDimensions(int width, int height) {
+        int index = getPoolIndexForDimensions(width, height);
+        if (index == POOL_INDEX_NONE) {
+            return null;
+        } else {
+            return mPools[index];
+        }
+    }
+
+    private int getPoolIndexForDimensions(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return POOL_INDEX_NONE;
+        }
+        if (width == height) {
+            return POOL_INDEX_SQUARE;
+        }
+        int min, max;
+        if (width > height) {
+            min = height;
+            max = width;
+        } else {
+            min = width;
+            max = height;
+        }
+        for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) {
+            if (min * ar.x == max * ar.y) {
+                return POOL_INDEX_PHOTO;
+            }
+        }
+        return POOL_INDEX_MISC;
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        int total = 0;
+        for (SparseArrayBitmapPool p : mPools) {
+            total += p.getSize();
+        }
+        return total;
+    }
+
+    public Bitmap get(int width, int height) {
+        SparseArrayBitmapPool pool = getPoolForDimensions(width, height);
+        if (pool == null) {
+            return null;
+        } else {
+            return pool.get(width, height);
+        }
+    }
+
+    public boolean put(Bitmap b) {
+        if (b == null) {
+            return false;
+        }
+        SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight());
+        if (pool == null) {
+            b.recycle();
+            return false;
+        } else {
+            return pool.put(b);
+        }
+    }
+
+    public void clear() {
+        for (SparseArrayBitmapPool p : mPools) {
+            p.clear();
+        }
+    }
+}
diff --git a/src/com/android/photos/data/MediaSetLoader.java b/src/com/android/photos/data/MediaSetLoader.java
new file mode 100644
index 0000000..4afb7d9
--- /dev/null
+++ b/src/com/android/photos/data/MediaSetLoader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos.data;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+
+/**
+ * Proof of concept, don't use
+ */
+public class MediaSetLoader extends AsyncTaskLoader<MediaSet> {
+
+    private static final SyncListener sNullListener = new SyncListener() {
+        @Override
+        public void onSyncDone(MediaSet mediaSet, int resultCode) {
+        }
+    };
+
+    private MediaSet mMediaSet;
+    private Future<Integer> mSyncTask = null;
+    private ContentListener mObserver = new ContentListener() {
+        @Override
+        public void onContentDirty() {
+            onContentChanged();
+        }
+    };
+
+    public MediaSetLoader(Context context, String path) {
+        super(context);
+        mMediaSet = DataManager.from(getContext()).getMediaSet(path);
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        mMediaSet.addContentListener(mObserver);
+        mSyncTask = mMediaSet.requestSync(sNullListener);
+        forceLoad();
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        if (mSyncTask != null) {
+            mSyncTask.cancel();
+            mSyncTask = null;
+        }
+        return super.onCancelLoad();
+    }
+
+    @Override
+    protected void onStopLoading() {
+        super.onStopLoading();
+        cancelLoad();
+        mMediaSet.removeContentListener(mObserver);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        onStopLoading();
+    }
+
+    @Override
+    public MediaSet loadInBackground() {
+        mMediaSet.loadIfDirty();
+        return mMediaSet;
+    }
+
+}
diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java
new file mode 100644
index 0000000..8c511a5
--- /dev/null
+++ b/src/com/android/photos/data/PhotoSetLoader.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos.data;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Files.FileColumns;
+
+public class PhotoSetLoader extends CursorLoader {
+
+    private static final Uri CONTENT_URI = Files.getContentUri("external");
+    private static final String[] PROJECTION = new String[] {
+        FileColumns._ID,
+        FileColumns.DATA,
+        FileColumns.WIDTH,
+        FileColumns.HEIGHT,
+        FileColumns.DATE_ADDED,
+        FileColumns.MEDIA_TYPE,
+    };
+    private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC";
+    private static final String SELECTION =
+            FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE
+            + " OR "
+            + FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_VIDEO;
+
+    public static final int INDEX_ID = 0;
+    public static final int INDEX_DATA = 1;
+    public static final int INDEX_WIDTH = 2;
+    public static final int INDEX_HEIGHT = 3;
+    public static final int INDEX_DATE_ADDED = 4;
+    public static final int INDEX_MEDIA_TYPE = 5;
+
+    private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/");
+    private final ContentObserver mGlobalObserver = new ForceLoadContentObserver();
+
+    public PhotoSetLoader(Context context) {
+        super(context, CONTENT_URI, PROJECTION, SELECTION, null, SORT_ORDER);
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        getContext().getContentResolver().registerContentObserver(GLOBAL_CONTENT_URI,
+                true, mGlobalObserver);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        getContext().getContentResolver().unregisterContentObserver(mGlobalObserver);
+    }
+}
diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java
new file mode 100644
index 0000000..8512590
--- /dev/null
+++ b/src/com/android/photos/data/SparseArrayBitmapPool.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos.data;
+
+import android.graphics.Bitmap;
+import android.util.SparseArray;
+
+import android.util.Pools.Pool;
+
+public class SparseArrayBitmapPool {
+
+    private static final int BITMAPS_TO_KEEP_AFTER_UNNEEDED_HINT = 4;
+    private int mCapacityBytes;
+    private SparseArray<Node> mStore = new SparseArray<Node>();
+    private int mSizeBytes = 0;
+
+    private Pool<Node> mNodePool;
+    private Node mPoolNodesHead = null;
+    private Node mPoolNodesTail = null;
+
+    protected static class Node {
+        Bitmap bitmap;
+        Node prevInBucket;
+        Node nextInBucket;
+        Node nextInPool;
+        Node prevInPool;
+    }
+
+    public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) {
+        mCapacityBytes = capacityBytes;
+        mNodePool = nodePool;
+    }
+
+    public synchronized void setCapacity(int capacityBytes) {
+        mCapacityBytes = capacityBytes;
+        freeUpCapacity(0);
+    }
+
+    private void freeUpCapacity(int bytesNeeded) {
+        int targetSize = mCapacityBytes - bytesNeeded;
+        while (mPoolNodesTail != null && mSizeBytes > targetSize) {
+            unlinkAndRecycleNode(mPoolNodesTail, true);
+        }
+    }
+
+    private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) {
+        // Remove the node from its spot in its bucket
+        if (n.prevInBucket != null) {
+            n.prevInBucket.nextInBucket = n.nextInBucket;
+        } else {
+            mStore.put(n.bitmap.getWidth(), n.nextInBucket);
+        }
+        if (n.nextInBucket != null) {
+            n.nextInBucket.prevInBucket = n.prevInBucket;
+        }
+
+        // Remove the node from its spot in the list of pool nodes
+        if (n.prevInPool != null) {
+            n.prevInPool.nextInPool = n.nextInPool;
+        } else {
+            mPoolNodesHead = n.nextInPool;
+        }
+        if (n.nextInPool != null) {
+            n.nextInPool.prevInPool = n.prevInPool;
+        } else {
+            mPoolNodesTail = n.prevInPool;
+        }
+
+        // Recycle the node
+        n.nextInBucket = null;
+        n.nextInPool = null;
+        n.prevInBucket = null;
+        n.prevInPool = null;
+        mSizeBytes -= n.bitmap.getByteCount();
+        if (recycleBitmap) n.bitmap.recycle();
+        n.bitmap = null;
+        mNodePool.release(n);
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        return mSizeBytes;
+    }
+
+    public synchronized Bitmap get(int width, int height) {
+        Node cur = mStore.get(width);
+        while (cur != null) {
+            if (cur.bitmap.getHeight() == height) {
+                Bitmap b = cur.bitmap;
+                unlinkAndRecycleNode(cur, false);
+                return b;
+            }
+            cur = cur.nextInBucket;
+        }
+        return null;
+    }
+
+    public synchronized boolean put(Bitmap b) {
+        if (b == null) {
+            return false;
+        }
+        int bytes = b.getByteCount();
+        freeUpCapacity(bytes);
+        Node newNode = mNodePool.acquire();
+        if (newNode == null) {
+            newNode = new Node();
+        }
+        newNode.bitmap = b;
+        newNode.prevInBucket = null;
+        newNode.prevInPool = null;
+        newNode.nextInPool = mPoolNodesHead;
+        mPoolNodesHead = newNode;
+        int key = b.getWidth();
+        newNode.nextInBucket = mStore.get(key);
+        if (newNode.nextInBucket != null) {
+            newNode.nextInBucket.prevInBucket = newNode;
+        }
+        mStore.put(key, newNode);
+        if (newNode.nextInPool == null) {
+            mPoolNodesTail = newNode;
+        }
+        mSizeBytes += bytes;
+        return true;
+    }
+
+    public synchronized void clear() {
+        freeUpCapacity(mCapacityBytes);
+    }
+}
diff --git a/tests/src/com/android/gallery3d/StressTests.java b/tests/src/com/android/gallery3d/StressTests.java
index 32eefdc..b991e9e 100755
--- a/tests/src/com/android/gallery3d/StressTests.java
+++ b/tests/src/com/android/gallery3d/StressTests.java
@@ -41,7 +41,7 @@
         result.addTestSuite(CameraLatency.class);
         result.addTestSuite(CameraStartUp.class);
         result.addTestSuite(ImageCapture.class);
-        result.addTestSuite(SwitchPreview.class);
+//      result.addTestSuite(SwitchPreview.class);
         return result;
     }
 }
diff --git a/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java
index 57ae691..d3fb10d 100755
--- a/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java
+++ b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java
@@ -25,8 +25,8 @@
 
     // Default recorder settings
     public static int mVideoDuration = 20000; // set default to 20 seconds
-    public static int mVideoIterations = 100; // set default to 100 videos
-    public static int mImageIterations = 100; // set default to 100 images
+    public static int mVideoIterations = 1; // set default to 1 video
+    public static int mImageIterations = 10; // set default to 10 images
 
     @Override
     public TestSuite getAllTests() {