Merge "Open album in filmstrip only if the item exists." into gb-ub-photos-arches
diff --git a/Android.mk b/Android.mk
index 1e4430b..e2914bb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,7 +8,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES += com.android.gallery3d.common2
 LOCAL_STATIC_JAVA_LIBRARIES += mp4parser
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../Camera/src)
 
diff --git a/jni/Android.mk b/jni/Android.mk
index f6c3c81..935abeb 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -32,6 +32,7 @@
                    filters/hue.c \
                    filters/shadows.c \
                    filters/hsv.c \
+                   filters/vibrance.c \
 		   filters/vignette.c
 
 LOCAL_CFLAGS    += -ffast-math -O3 -funroll-loops
diff --git a/jni/filters/shadows.c b/jni/filters/shadows.c
index da3e33c..f812b93 100644
--- a/jni/filters/shadows.c
+++ b/jni/filters/shadows.c
@@ -34,6 +34,6 @@
      }
 
      (*env)->ReleaseShortArrayElements(env, vlut, lut, 0);
-     free(lut);
+     free(hsv);
      AndroidBitmap_unlockPixels(env, bitmap);
  }
diff --git a/jni/filters/vibrance.c b/jni/filters/vibrance.c
new file mode 100644
index 0000000..cb5c536
--- /dev/null
+++ b/jni/filters/vibrance.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math.h>
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterVibrance, nativeApplyFilter, jobject bitmap, jint width, jint height,  jfloat vibrance)
+{
+    char* destination = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+    int i;
+    int len = width * height * 4;
+    float Rf = 0.2999f;
+    float Gf = 0.587f;
+    float Bf = 0.114f;
+    float Vib = vibrance/100.f;
+    float S  = Vib+1;
+    float MS = 1.0f - S;
+    float Rt = Rf * MS;
+    float Gt = Gf * MS;
+    float Bt = Bf * MS;
+    float R, G, B;
+    for (i = 0; i < len; i+=4)
+    {
+        int r = destination[RED];
+        int g = destination[GREEN];
+        int b = destination[BLUE];
+        float red = (r-MAX(g, b))/256.f;
+        float sx = (float)(Vib/(1+exp(-red*3)));
+        S = sx+1;
+        MS = 1.0f - S;
+        Rt = Rf * MS;
+        Gt = Gf * MS;
+        Bt = Bf * MS;
+        int t = (r + g) / 2;
+        R = r;
+        G = g;
+        B = b;
+
+        float Rc = R * (Rt + S) + G * Gt + B * Bt;
+        float Gc = R * Rt + G * (Gt + S) + B * Bt;
+        float Bc = R * Rt + G * Gt + B * (Bt + S);
+
+        destination[RED] = CLAMP(Rc);
+        destination[GREEN] = CLAMP(Gc);
+        destination[BLUE] = CLAMP(Bc);
+    }
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
index 0dead60..1449f16 100644
--- a/res/layout/filtershow_activity.xml
+++ b/res/layout/filtershow_activity.xml
@@ -125,6 +125,12 @@
                 android:layout_height="wrap_content"
                 android:visibility="gone" />
 
+            <com.android.gallery3d.filtershow.imageshow.ImageZoom
+                android:id="@+id/imageZoom"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone" />
+
             <ImageButton
                 android:id="@+id/showOriginalButton"
                 android:layout_width="64dip"
diff --git a/res/menu/filtershow_activity_menu.xml b/res/menu/filtershow_activity_menu.xml
index 8987e48..7cf4466 100644
--- a/res/menu/filtershow_activity_menu.xml
+++ b/res/menu/filtershow_activity_menu.xml
@@ -16,6 +16,9 @@
         android:showAsAction="never"
         android:title="@string/filtershow_redo"/>
     <item
+        android:id="@+id/resetHistoryButton"
+        android:title="@string/reset"/>
+    <item
         android:id="@+id/operationsButton"
         android:icon="@drawable/filtershow_button_operations"
         android:showAsAction="never"
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 5619f92..526360d 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Няма налично външно хранилище"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Филмова лента"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Таблица"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Отрязва се"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Моля, изчакайте"</string>
+    <string name="save_into" msgid="6688364520925473396">"Запазване на отрязания видеоклип във:"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Не може да се отреже: Целевият видеоклип е твърде кратък"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Не се отряза: Същата дължина като оригинала"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c465a00..3dd08d3 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Žádné externí úložiště není k dispozici"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Filmový pás"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Mřížka"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Zkrácení"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Čekejte prosím"</string>
+    <string name="save_into" msgid="6688364520925473396">"Uložit zkrácené video do"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Zkrácení nelze provést: výsledné video je příliš krátké"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Zkrácení nebylo provedeno: délka videa je stejná jako délka originálu"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index bec9b18..8fa6cda 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Intet tilgængeligt eksternt lager"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Filmstrimmel"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Gitter"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Beskæring"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Vent"</string>
+    <string name="save_into" msgid="6688364520925473396">"Gem beskåret video i"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Der kan ikke beskæres – målvideoen er for kort"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Blev ikke beskåret – samme længde som oprindeligt"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 978ce2f..baedb0e 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"No hay almacenamiento externo disponible."</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Tira de película"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Cuadrícula"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Recortando"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Espera."</string>
+    <string name="save_into" msgid="6688364520925473396">"Guardar video recortado en"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"No se puede recortar: el video de destino es demasiado corto."</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"No recortar: misma duración que el original"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 1a8eb23..b9152e9 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"هیچ دستگاه ذخیره خارجی دردسترس نیست"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"نوار فیلم"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"شبکه"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"کوتاه کردن"</string>
+    <string name="please_wait" msgid="7296066089146487366">"لطفاً منتظر بمانید"</string>
+    <string name="save_into" msgid="6688364520925473396">"ذخیره ویدیوی کوتاه شده در"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"نمی‌تواند کوتاه شود: ویدیوی موردنظر بسیار کوتاه است"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"کوتاه نشد: طول ویدیو با ویدیوی اصلی یکسان است"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 02974ce..ea61bc1 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Tidak ada penyimpanan eksternal yang tersedia"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Strip film"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Kisi"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Pemangkasan"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Harap tunggu"</string>
+    <string name="save_into" msgid="6688364520925473396">"Simpan video yang dipangkas ke dalam"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Tidak dapat memangkas : video target terlalu pendek"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Tidak dipangkas : panjang sama dengan aslinya"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 0a18b71..18b1883 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"利用できる外部ストレージがありません"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"フィルムストリップ"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"グリッド"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"トリミング"</string>
+    <string name="please_wait" msgid="7296066089146487366">"お待ちください"</string>
+    <string name="save_into" msgid="6688364520925473396">"トリミングした動画を次の場所に保存します:"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"トリミングできません: 動画が短すぎます"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"トリミングされていません: 元の動画と同じ長さです"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index d54ef49..20d73a8 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"사용할 수 있는 외부 저장소 없음"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"슬라이드"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"바둑판"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"자르기"</string>
+    <string name="please_wait" msgid="7296066089146487366">"잠시 기다려 주세요."</string>
+    <string name="save_into" msgid="6688364520925473396">"잘라낸 동영상 저장 위치"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"잘라낼 수 없음 : 동영상이 너무 짧음"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"잘라내지 않음 : 원본과 길이가 같음"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 2f238d2..7480158 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nenhum armazenamento externo disponível"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Película fotográfica"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Grade"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Cortando"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Aguarde"</string>
+    <string name="save_into" msgid="6688364520925473396">"Salvar vídeo cortado em"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Impossível cortar: o vídeo de destino é curto demais"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Impossível cortar: mesma duração da origem"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 692e602..8fc0152 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Нет внешних накопителей"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Кинолента"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Сетка"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Обрезка"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Подождите…"</string>
+    <string name="save_into" msgid="6688364520925473396">"Теперь видео можно сохранить"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Нельзя обрезать видео: оно слишком короткое"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Видео не было обрезано"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 5a405b5..a0d44bc 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"K dispozícii nie je žiadny externý ukladací priestor"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Filmový pás"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Mriežka"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Orezanie"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Čakajte"</string>
+    <string name="save_into" msgid="6688364520925473396">"Uložiť orezané video do"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Nie je možné orezať: výsledné video je príliš krátke"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Neorezané video: dĺžka videa je rovnaká ako dĺžka originálu"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 3e3ffe5..90ca26b 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Kullanılabilir harici depolama yok"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Film şeridi"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Tablo"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Kırpma"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Lütfen bekleyin"</string>
+    <string name="save_into" msgid="6688364520925473396">"Kırpılan videoları şuraya kaydet:"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Kırpılamaz: hedef video çok kısa"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Kırpılmadı: orijinal ile aynı uzunlukta"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index c68b132..b6ff2e9 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -181,14 +181,9 @@
     <string name="no_external_storage" msgid="95726173164068417">"Không có bộ nhớ ngoài nào"</string>
     <string name="switch_photo_filmstrip" msgid="991949386967109493">"Cuộn phim"</string>
     <string name="switch_photo_grid" msgid="6533267664294518167">"Lưới"</string>
-    <!-- no translation found for trimming (9122385768369143997) -->
-    <skip />
-    <!-- no translation found for please_wait (7296066089146487366) -->
-    <skip />
-    <!-- no translation found for save_into (6688364520925473396) -->
-    <skip />
-    <!-- no translation found for trim_too_short (751593965620665326) -->
-    <skip />
-    <!-- no translation found for trim_too_long (2657958275279217174) -->
-    <skip />
+    <string name="trimming" msgid="9122385768369143997">"Đang cắt ngắn"</string>
+    <string name="please_wait" msgid="7296066089146487366">"Vui lòng chờ"</string>
+    <string name="save_into" msgid="6688364520925473396">"Lưu video đã được cắt ngắn vào"</string>
+    <string name="trim_too_short" msgid="751593965620665326">"Không thể cắt ngắn : video đích quá ngắn"</string>
+    <string name="trim_too_long" msgid="2657958275279217174">"Chưa cắt ngắn : độ dài như ban đầu"</string>
 </resources>
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index 6736d35..9cc56cc 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -401,7 +401,9 @@
                 mParentMediaSetString != null;
         GalleryActionBar actionBar = mActivity.getGalleryActionBar();
         actionBar.setDisplayOptions(enableHomeButton, false);
-        actionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_GRID_MODE_SELECTED, this);
+        if (!mGetContent) {
+            actionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_GRID_MODE_SELECTED, this);
+        }
 
         // Set the reload bit here to prevent it exit this page in clearLoadingBit().
         setLoadingBit(BIT_LOADING_RELOAD);
@@ -425,7 +427,9 @@
         mAlbumDataAdapter.pause();
         mAlbumView.pause();
         DetailsHelper.pause();
-        mActivity.getGalleryActionBar().disableAlbumModeMenu(true);
+        if (!mGetContent) {
+            mActivity.getGalleryActionBar().disableAlbumModeMenu(true);
+        }
 
         if (mSyncTask != null) {
             mSyncTask.cancel();
diff --git a/src/com/android/gallery3d/app/GalleryApp.java b/src/com/android/gallery3d/app/GalleryApp.java
index a2d7494..b56b8a8 100644
--- a/src/com/android/gallery3d/app/GalleryApp.java
+++ b/src/com/android/gallery3d/app/GalleryApp.java
@@ -28,6 +28,8 @@
 
 public interface GalleryApp {
     public DataManager getDataManager();
+
+    public StitchingProgressManager getStitchingProgressManager();
     public ImageCacheService getImageCacheService();
     public DownloadCache getDownloadCache();
     public ThreadPool getThreadPool();
diff --git a/src/com/android/gallery3d/app/GalleryAppImpl.java b/src/com/android/gallery3d/app/GalleryAppImpl.java
index 9576093..c4507b3 100644
--- a/src/com/android/gallery3d/app/GalleryAppImpl.java
+++ b/src/com/android/gallery3d/app/GalleryAppImpl.java
@@ -30,6 +30,7 @@
 import com.android.gallery3d.photoeditor.PhotoEditor;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.LightCycleHelper;
 import com.android.gallery3d.util.ThreadPool;
 
 import java.io.File;
@@ -44,6 +45,7 @@
     private DataManager mDataManager;
     private ThreadPool mThreadPool;
     private DownloadCache mDownloadCache;
+    private StitchingProgressManager mStitchingProgressManager;
 
     @Override
     public void onCreate() {
@@ -59,6 +61,8 @@
         getPackageManager().setComponentEnabledSetting(
                 new ComponentName(this, PhotoEditor.class),
                 state, PackageManager.DONT_KILL_APP);
+
+        mStitchingProgressManager = LightCycleHelper.createStitchingManagerInstance(this);
     }
 
     @Override
@@ -76,6 +80,11 @@
     }
 
     @Override
+    public StitchingProgressManager getStitchingProgressManager() {
+        return mStitchingProgressManager;
+    }
+
+    @Override
     public ImageCacheService getImageCacheService() {
         // This method may block on file I/O so a dedicated lock is needed here.
         synchronized (mLock) {
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 3c64fa3..2a088b1 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -371,6 +371,11 @@
                     mSetPathString = "/filter/empty/{"+mSetPathString+"}";
                 }
 
+                // Add support for showing panorama progress.
+                if (LightCycleHelper.hasLightCycleCapture(mActivity.getAndroidContext())) {
+                    mSetPathString = LightCycleHelper.wrapGalleryPath(mSetPathString);
+                }
+
                 // Combine the original MediaSet with the one for ScreenNail
                 // from AppBridge.
                 mSetPathString = "/combo/item/{" + screenNailSetPath +
@@ -578,8 +583,9 @@
         }
 
         Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
-        intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags(
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        intent.setDataAndType(current.getContentUri(), current.getMimeType())
+                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         if (mActivity.getPackageManager()
                 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
             intent.setAction(Intent.ACTION_EDIT);
diff --git a/src/com/android/gallery3d/app/StitchingChangeListener.java b/src/com/android/gallery3d/app/StitchingChangeListener.java
new file mode 100644
index 0000000..901f379
--- /dev/null
+++ b/src/com/android/gallery3d/app/StitchingChangeListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.app;
+
+
+public interface StitchingChangeListener {
+    public void onFileAdded(String filePath);
+
+    public void onFileRemoved(String filePath);
+
+    public void onProgressChanged(String filePath, int progress);
+}
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index eeab8a8..e3b7bfc 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -28,6 +28,7 @@
 import com.android.gallery3d.data.MediaSet.ItemConsumer;
 import com.android.gallery3d.data.MediaSource.PathId;
 import com.android.gallery3d.picasasource.PicasaSource;
+import com.android.gallery3d.util.LightCycleHelper;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -130,6 +131,7 @@
         addSource(new SecureSource(mApplication));
         addSource(new UriSource(mApplication));
         addSource(new SnailSource(mApplication));
+        addSource(LightCycleHelper.createMediaSourceInstance(mApplication));
 
         if (mActiveCount > 0) {
             for (MediaSource source : mSourceMap.values()) {
@@ -153,6 +155,7 @@
 
     // open for debug
     void addSource(MediaSource source) {
+        if (source == null) return;
         mSourceMap.put(source.getPrefix(), source);
     }
 
diff --git a/src/com/android/gallery3d/data/LocalAlbumSet.java b/src/com/android/gallery3d/data/LocalAlbumSet.java
index d737ca8..afaac49 100644
--- a/src/com/android/gallery3d/data/LocalAlbumSet.java
+++ b/src/com/android/gallery3d/data/LocalAlbumSet.java
@@ -26,6 +26,7 @@
 import com.android.gallery3d.data.BucketHelper.BucketEntry;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.LightCycleHelper;
 import com.android.gallery3d.util.MediaSetUtils;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -92,7 +93,7 @@
     }
 
     private static int findBucket(BucketEntry entries[], int bucketId) {
-        for (int i = 0, n = entries.length; i < n ; ++i) {
+        for (int i = 0, n = entries.length; i < n; ++i) {
             if (entries[i].bucketId == bucketId) return i;
         }
         return -1;
@@ -127,6 +128,11 @@
             for (BucketEntry entry : entries) {
                 MediaSet album = getLocalAlbum(dataManager,
                         mType, mPath, entry.bucketId, entry.bucketName);
+                if (LightCycleHelper.hasLightCycleCapture(mApplication.getAndroidContext())
+                        && album.isCameraRoll()) {
+                    album = dataManager.getMediaSet(Path.fromString(
+                            LightCycleHelper.wrapGalleryPath(album.getPath().toString())));
+                }
                 albums.add(album);
             }
             return albums;
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 694f512..964c950 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -11,6 +11,7 @@
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
 import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
 import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.imageshow.ImageZoom;
 import com.android.gallery3d.filtershow.presets.*;
 import com.android.gallery3d.filtershow.provider.SharedImageProvider;
 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
@@ -19,6 +20,7 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.renderscript.RenderScript;
 import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.Activity;
@@ -57,6 +59,7 @@
     private ImageCurves mImageCurves = null;
     private ImageBorder mImageBorders = null;
     private ImageStraighten mImageStraighten = null;
+    private ImageZoom mImageZoom = null;
 
     private View mListFx = null;
     private View mListBorders = null;
@@ -100,6 +103,8 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        ImageFilterRS.setRenderScriptContext(this);
+
         setContentView(R.layout.filtershow_activity);
         ActionBar actionBar = getActionBar();
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
@@ -121,11 +126,13 @@
         mImageCurves = (ImageCurves) findViewById(R.id.imageCurves);
         mImageBorders = (ImageBorder) findViewById(R.id.imageBorder);
         mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten);
+        mImageZoom = (ImageZoom) findViewById(R.id.imageZoom);
 
         mImageViews.add(mImageShow);
         mImageViews.add(mImageCurves);
         mImageViews.add(mImageBorders);
         mImageViews.add(mImageStraighten);
+        mImageViews.add(mImageZoom);
 
         mListFx = findViewById(R.id.fxList);
         mListBorders = findViewById(R.id.bordersList);
@@ -211,6 +218,8 @@
         mImageBorders.setMaster(mImageShow);
         mImageStraighten.setImageLoader(mImageLoader);
         mImageStraighten.setMaster(mImageShow);
+        mImageZoom.setImageLoader(mImageLoader);
+        mImageZoom.setMaster(mImageShow);
 
         Intent intent = getIntent();
         String data = intent.getDataString();
@@ -304,6 +313,10 @@
                 invalidateViews();
                 return true;
             }
+            case R.id.resetHistoryButton: {
+                resetHistory();
+                return true;
+            }
             case R.id.showImageStateButton: {
                 toggleImageStatePanel();
                 return true;
@@ -688,11 +701,23 @@
             @Override
             public void onClick(View v) {
                 hideImageViews();
-                mImageShow.setVisibility(View.VISIBLE);
+                mImageZoom.setVisibility(View.VISIBLE);
+                mImageZoom.setShowControls(true);
+                ImagePreset preset = mImageZoom.getImagePreset();
+                ImageFilter filter = preset.getFilter("Sharpen");
+                if (filter == null) {
+                    ImageFilterSharpen sharpen = new ImageFilterSharpen();
+                    ImagePreset copy = new ImagePreset(preset);
+                    copy.add(sharpen);
+                    copy.setHistoryName(sharpen.getName());
+                    copy.setIsFx(false);
+                    filter = copy.getFilter("Sharpen");
+                    mImageZoom.setImagePreset(copy);
+                }
+                mImageZoom.setCurrentFilter(filter);
                 unselectPanelButtons(mColorsPanelButtons);
                 mSharpenButton.setSelected(true);
-                mImageShow.showToast("Sharpen", true);
-                mImageShow.setCurrentFilter(null);
+                invalidateViews();
             }
         };
     }
@@ -783,14 +808,14 @@
                 mImageShow.setVisibility(View.VISIBLE);
                 mImageShow.setShowControls(true);
                 ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Shadows");
+                ImageFilter filter = preset.getFilter("Vibrance");
                 if (filter == null) {
-                    ImageFilterShadows contrast = new ImageFilterShadows();
+                    ImageFilterVibrance contrast = new ImageFilterVibrance();
                     ImagePreset copy = new ImagePreset(preset);
                     copy.add(contrast);
                     copy.setHistoryName(contrast.getName());
                     copy.setIsFx(false);
-                    filter = copy.getFilter("Shadows");
+                    filter = copy.getFilter("Vibrance");
                     mImageShow.setImagePreset(copy);
                 }
                 mImageShow.setCurrentFilter(filter);
@@ -835,14 +860,14 @@
                 mImageShow.setVisibility(View.VISIBLE);
                 mImageShow.setShowControls(true);
                 ImagePreset preset = mImageShow.getImagePreset();
-                ImageFilter filter = preset.getFilter("Hue");
+                ImageFilter filter = preset.getFilter("Shadows");
                 if (filter == null) {
-                    ImageFilterHue contrast = new ImageFilterHue();
+                    ImageFilterShadows contrast = new ImageFilterShadows();
                     ImagePreset copy = new ImagePreset(preset);
                     copy.add(contrast);
                     copy.setHistoryName(contrast.getName());
                     copy.setIsFx(false);
-                    filter = copy.getFilter("Hue");
+                    filter = copy.getFilter("Shadows");
                     mImageShow.setImagePreset(copy);
                 }
                 mImageShow.setCurrentFilter(filter);
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index 100a17b..9944f5f 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -1,34 +1,37 @@
 
 package com.android.gallery3d.filtershow.cache;
 
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Vector;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-import com.android.gallery3d.filtershow.tools.SaveCopyTask;
-import com.android.gallery3d.filtershow.tools.ProcessedBitmap;
-import com.android.gallery3d.R;
-
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
 import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.media.ExifInterface;
 import android.net.Uri;
 import android.provider.MediaStore;
 import android.util.Log;
 
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.tools.ProcessedBitmap;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
 public class ImageLoader {
 
     private static final String LOGTAG = "ImageLoader";
@@ -41,6 +44,7 @@
 
     private Cache mCache = new DelayedPresetCache(30);
     private Cache mHiresCache = new DelayedPresetCache(2);
+    private ZoomCache mZoomCache = new ZoomCache();
 
     private int mOrientation = 0;
     private HistoryAdapter mAdapter = null;
@@ -49,6 +53,8 @@
     private Context mContext = null;
     private Uri mUri = null;
 
+    private Rect mOriginalBounds = null;
+
     public ImageLoader(Context context) {
         mContext = context;
     }
@@ -65,7 +71,15 @@
         return mUri;
     }
 
+    public Rect getOriginalBounds() {
+        return mOriginalBounds;
+    }
+
     private int getOrientation(Uri uri) {
+        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+            return getOrientationFromPath(uri.getPath());
+        }
+
         Cursor cursor = null;
         try {
             cursor = mContext.getContentResolver().query(uri,
@@ -73,17 +87,12 @@
                         MediaStore.Images.ImageColumns.ORIENTATION
                     },
                     null, null, null);
+            return cursor.moveToNext() ? cursor.getInt(0) : -1;
         } catch (SQLiteException e){
-            Utils.closeSilently(cursor);
             return ExifInterface.ORIENTATION_UNDEFINED;
+        } finally {
+            Utils.closeSilently(cursor);
         }
-
-        if (cursor.getCount() != 1) {
-            return -1;
-        }
-
-        cursor.moveToFirst();
-        return cursor.getInt(0);
     }
 
     private int getOrientationFromPath(String path) {
@@ -125,6 +134,22 @@
         }
     }
 
+    private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
+        InputStream is = null;
+        try {
+            is = mContext.getContentResolver().openInputStream(uri);
+            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+            return decoder.decodeRegion(bounds, null);
+        } catch (FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException: " + uri);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(is);
+        }
+        return null;
+    }
+
     private Bitmap loadScaledBitmap(Uri uri, int size) {
         InputStream is = null;
         try {
@@ -137,6 +162,9 @@
 
             int width_tmp = o.outWidth;
             int height_tmp = o.outHeight;
+
+            mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
+
             int scale = 1;
             while (true) {
                 if (width_tmp / 2 < size || height_tmp / 2 < size)
@@ -192,6 +220,24 @@
         }
     }
 
+    // TODO: this currently does the loading + filtering on the UI thread -- need to
+    // move this to a background thread.
+    public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
+            boolean force) {
+        Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
+        if (force || bmp == null) {
+            bmp = loadRegionBitmap(mUri, bounds);
+            if (bmp != null) {
+                // TODO: this workaround for RS might not be needed ultimately
+                Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
+                imagePreset.apply(bmp2);
+                mZoomCache.setImage(imagePreset, bounds, bmp2);
+                return bmp2;
+            }
+        }
+        return bmp;
+    }
+
     // Caching method
     public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
             boolean hiRes) {
@@ -223,6 +269,7 @@
     public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
         mHiresCache.reset(imagePreset);
         mCache.reset(imagePreset);
+        mZoomCache.reset(imagePreset);
     }
 
     public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
diff --git a/src/com/android/gallery3d/filtershow/cache/ZoomCache.java b/src/com/android/gallery3d/filtershow/cache/ZoomCache.java
new file mode 100644
index 0000000..e6e7f14
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/ZoomCache.java
@@ -0,0 +1,39 @@
+package com.android.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class ZoomCache {
+
+    private ImagePreset mImagePreset = null;
+    private Bitmap mBitmap = null;
+    private Rect mBounds = null;
+
+    // TODO: move the processing to a background thread...
+    public Bitmap getImage(ImagePreset preset, Rect bounds) {
+        if (mBounds != bounds) {
+            return null;
+        }
+        if (mImagePreset == null) {
+            return null;
+        }
+        if (!mImagePreset.same(preset)) {
+            return null;
+        }
+        return mBitmap;
+    }
+
+    public void setImage(ImagePreset preset, Rect bounds, Bitmap bitmap) {
+        mBitmap = bitmap;
+        mBounds = bounds;
+        mImagePreset = preset;
+    }
+
+    public void reset(ImagePreset imagePreset) {
+        if (imagePreset == mImagePreset) {
+            mBitmap = null;
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index c2bd952..c039fce 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -2,11 +2,13 @@
 package com.android.gallery3d.filtershow.filters;
 
 import android.graphics.Bitmap;
+import android.util.Log;
 
 public class ImageFilter implements Cloneable {
 
     protected int mParameter = 0;
     protected String mName = "Original";
+    private final String LOGTAG = "ImageFilter";
 
     @Override
     public ImageFilter clone() throws CloneNotSupportedException {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
new file mode 100644
index 0000000..ab2d304
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -0,0 +1,59 @@
+package com.android.gallery3d.filtershow.filters;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.util.Log;
+
+public class ImageFilterRS extends ImageFilter {
+    private final String LOGTAG = "ImageFilterRS";
+
+    private static RenderScript mRS = null;
+    protected static Allocation mInPixelsAllocation;
+    protected static Allocation mOutPixelsAllocation;
+    private static android.content.res.Resources mResources = null;
+
+    public void prepare(Bitmap bitmap) {
+        mInPixelsAllocation = Allocation.createFromBitmap(mRS, bitmap,
+                Allocation.MipmapControl.MIPMAP_NONE,
+                Allocation.USAGE_SCRIPT);
+        mOutPixelsAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType());
+    }
+
+    public void createFilter(android.content.res.Resources res) {
+    }
+
+    public void runFilter() {
+    }
+
+    public void update(Bitmap bitmap) {
+        mOutPixelsAllocation.copyTo(bitmap);
+    }
+
+    public void apply(Bitmap bitmap) {
+        if (bitmap == null) {
+            return;
+        }
+        try {
+            prepare(bitmap);
+            createFilter(mResources);
+            runFilter();
+            update(bitmap);
+        } catch (android.renderscript.RSIllegalArgumentException e) {
+            Log.e(LOGTAG, "Illegal argument? " + e);
+        } catch (android.renderscript.RSRuntimeException e) {
+            Log.e(LOGTAG, "RS runtime exception ? " + e);
+        }
+    }
+
+    public static RenderScript getRenderScriptContext() {
+        return mRS;
+    }
+
+    public static void setRenderScriptContext(Activity context) {
+        mRS = RenderScript.create(context);
+        mResources = context.getResources();
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
new file mode 100644
index 0000000..3ec7b5f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
@@ -0,0 +1,52 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import android.renderscript.Element;
+import android.renderscript.Script;
+import android.renderscript.ScriptC;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterSharpen extends ImageFilterRS {
+
+    private static final String LOGTAG = "ImageFilterSharpen";
+    private ScriptC_convolve3x3 mScript;
+
+    public ImageFilterSharpen() {
+        mName = "Sharpen";
+    }
+
+    public void createFilter(android.content.res.Resources res) {
+        int w = mInPixelsAllocation.getType().getX();
+        int h = mInPixelsAllocation.getType().getY();
+
+        float p1 = mParameter;
+        float value = p1 / 100.0f;
+        float f[] = new float[9];
+        float p = value;
+        f[0] = -p;
+        f[1] = -p;
+        f[2] = -p;
+        f[3] = -p;
+        f[4] = 8 * p + 1;
+        f[5] = -p;
+        f[6] = -p;
+        f[7] = -p;
+        f[8] = -p;
+        if (mScript == null) {
+            mScript = new ScriptC_convolve3x3(getRenderScriptContext(), res, R.raw.convolve3x3);
+        }
+        mScript.set_gCoeffs(f);
+        mScript.set_gWidth(w);
+        mScript.set_gHeight(h);
+    }
+
+    public void runFilter() {
+        mScript.set_gIn(mInPixelsAllocation);
+        mScript.bind_gPixels(mInPixelsAllocation);
+        mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
new file mode 100644
index 0000000..fd437ee
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -0,0 +1,22 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterVibrance extends ImageFilter {
+
+    public ImageFilterVibrance() {
+        mName = "Vibrance";
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
+
+    @Override
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        int p = mParameter;
+        float value = p;
+        nativeApplyFilter(bitmap, w, h, value);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs b/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs
new file mode 100644
index 0000000..2acffab
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+#pragma rs_fp_relaxed
+
+int32_t gWidth;
+int32_t gHeight;
+const uchar4 *gPixels;
+rs_allocation gIn;
+
+float gCoeffs[9];
+
+void root(const uchar4 *in, uchar4 *out, const void *usrData, uint32_t x, uint32_t y) {
+    uint32_t x1 = min((int32_t)x+1, gWidth-1);
+    uint32_t x2 = max((int32_t)x-1, 0);
+    uint32_t y1 = min((int32_t)y+1, gHeight-1);
+    uint32_t y2 = max((int32_t)y-1, 0);
+
+    float4 p00 = rsUnpackColor8888(gPixels[x1 + gWidth * y1]);
+    float4 p01 = rsUnpackColor8888(gPixels[x + gWidth * y1]);
+    float4 p02 = rsUnpackColor8888(gPixels[x2 + gWidth * y1]);
+    float4 p10 = rsUnpackColor8888(gPixels[x1 + gWidth * y]);
+    float4 p11 = rsUnpackColor8888(gPixels[x + gWidth * y]);
+    float4 p12 = rsUnpackColor8888(gPixels[x2 + gWidth * y]);
+    float4 p20 = rsUnpackColor8888(gPixels[x1 + gWidth * y2]);
+    float4 p21 = rsUnpackColor8888(gPixels[x + gWidth * y2]);
+    float4 p22 = rsUnpackColor8888(gPixels[x2 + gWidth * y2]);
+
+    p00 *= gCoeffs[0];
+    p01 *= gCoeffs[1];
+    p02 *= gCoeffs[2];
+    p10 *= gCoeffs[3];
+    p11 *= gCoeffs[4];
+    p12 *= gCoeffs[5];
+    p20 *= gCoeffs[6];
+    p21 *= gCoeffs[7];
+    p22 *= gCoeffs[8];
+
+    p00 += p01;
+    p02 += p10;
+    p11 += p12;
+    p20 += p21;
+
+    p22 += p00;
+    p02 += p11;
+
+    p20 += p22;
+    p20 += p02;
+
+    p20 = clamp(p20, 0.f, 1.f);
+    *out = rsPackColorTo8888(p20.r, p20.g, p20.b);
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
index 00be826..f8c0142 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
@@ -13,9 +13,8 @@
 import android.graphics.drawable.NinePatchDrawable;
 import android.util.AttributeSet;
 
-public class ImageBorder extends ImageShow {
+public class ImageBorder extends ImageSlave {
     Paint gPaint = new Paint();
-    private ImageShow mMasterImageShow = null;
 
     public ImageBorder(Context context) {
         super(context);
@@ -25,18 +24,6 @@
         super(context, attrs);
     }
 
-    public void setMaster(ImageShow master) {
-        mMasterImageShow = master;
-    }
-
-    public ImagePreset getImagePreset() {
-        return mMasterImageShow.getImagePreset();
-    }
-
-    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
-        mMasterImageShow.setImagePreset(preset, addToHistory);
-    }
-
     public boolean showTitle() {
         return false;
     }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 2caa2d5..2a660d4 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -61,19 +61,31 @@
     private boolean mShowToast = false;
     private boolean mImportantToast = false;
 
+    protected float mTouchX = 0;
+    protected float mTouchY = 0;
+
     private Handler mHandler = new Handler();
 
     public void onNewValue(int value) {
-        if (mCurrentFilter != null) {
-            mCurrentFilter.setParameter(value);
+        if (getCurrentFilter() != null) {
+            getCurrentFilter().setParameter(value);
         }
-        if (mImagePreset != null) {
-            mImageLoader.resetImageForPreset(mImagePreset, this);
-            mImagePreset.fillImageStateAdapter(mImageStateAdapter);
+        if (getImagePreset() != null) {
+            mImageLoader.resetImageForPreset(getImagePreset(), this);
+            getImagePreset().fillImageStateAdapter(mImageStateAdapter);
         }
         invalidate();
     }
 
+    public void onTouchDown(float x, float y) {
+        mTouchX = x;
+        mTouchY = y;
+        invalidate();
+    }
+
+    public void onTouchUp() {
+    }
+
     public ImageShow(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSliderController.setListener(this);
@@ -102,6 +114,10 @@
         mCurrentFilter = filter;
     }
 
+    public ImageFilter getCurrentFilter() {
+        return mCurrentFilter;
+    }
+
     public void setAdapter(HistoryAdapter adapter) {
         mHistoryAdapter = adapter;
     }
@@ -125,6 +141,10 @@
         }, 400);
     }
 
+    public Rect getImageBounds() {
+        return mImageBounds;
+    }
+
     public ImagePreset getImagePreset() {
         return mImagePreset;
     }
@@ -161,55 +181,9 @@
     }
 
     public void onDraw(Canvas canvas) {
-        if (mBackgroundImage == null) {
-            mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
-        }
-        if (mBackgroundImage != null) {
-            Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
-                    mBackgroundImage.getHeight());
-            Rect d = new Rect(0, 0, getWidth(), getHeight());
-            canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
-        }
-
-        Bitmap filteredImage = null;
-        if (mImageLoader != null) {
-            filteredImage = mImageLoader.getImageForPreset(this,
-                    getImagePreset(), showHires());
-        }
-
-        if (filteredImage == null) {
-            // if no image for the current preset, use the previous one
-            filteredImage = mFilteredImage;
-        } else {
-            mFilteredImage = filteredImage;
-        }
-
-        if (mShowOriginal || mFilteredImage == null) {
-            mFilteredImage = mForegroundImage;
-        }
-
-        if (mFilteredImage != null) {
-            Rect s = new Rect(0, 0, mFilteredImage.getWidth(),
-                    mFilteredImage.getHeight());
-            float ratio = mFilteredImage.getWidth()
-                    / (float) mFilteredImage.getHeight();
-            float w = getWidth();
-            float h = w / ratio;
-            float ty = (getHeight() - h) / 2.0f;
-            float tx = 0;
-            // t = 0;
-            if (ratio < 1.0f) { // portrait image
-                h = getHeight();
-                w = h * ratio;
-                tx = (getWidth() - w) / 2.0f;
-                ty = 0;
-            }
-            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
-                    (int) (h + ty));
-            mImageBounds = d;
-
-            canvas.drawBitmap(mFilteredImage, s, d, mPaint);
-        }
+        drawBackground(canvas);
+        getFilteredImage();
+        drawImage(canvas, mFilteredImage);
 
         if (showTitle() && getImagePreset() != null) {
             mPaint.setARGB(200, 0, 0, 0);
@@ -232,6 +206,62 @@
         drawToast(canvas);
     }
 
+    public void getFilteredImage() {
+        Bitmap filteredImage = null;
+        if (mImageLoader != null) {
+            filteredImage = mImageLoader.getImageForPreset(this,
+                    getImagePreset(), showHires());
+        }
+
+        if (filteredImage == null) {
+            // if no image for the current preset, use the previous one
+            filteredImage = mFilteredImage;
+        } else {
+            mFilteredImage = filteredImage;
+        }
+
+        if (mShowOriginal || mFilteredImage == null) {
+            mFilteredImage = mForegroundImage;
+        }
+    }
+
+    public void drawImage(Canvas canvas, Bitmap image) {
+        if (image != null) {
+            Rect s = new Rect(0, 0, image.getWidth(),
+                    image.getHeight());
+            float ratio = image.getWidth()
+                    / (float) image.getHeight();
+            float w = getWidth();
+            float h = w / ratio;
+            float ty = (getHeight() - h) / 2.0f;
+            float tx = 0;
+            // t = 0;
+            if (ratio < 1.0f) { // portrait image
+                h = getHeight();
+                w = h * ratio;
+                tx = (getWidth() - w) / 2.0f;
+                ty = 0;
+            }
+            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
+                    (int) (h + ty));
+            mImageBounds = d;
+
+            canvas.drawBitmap(image, s, d, mPaint);
+        }
+    }
+
+    public void drawBackground(Canvas canvas) {
+        if (mBackgroundImage == null) {
+            mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
+        }
+        if (mBackgroundImage != null) {
+            Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
+                    mBackgroundImage.getHeight());
+            Rect d = new Rect(0, 0, getWidth(), getHeight());
+            canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
+        }
+    }
+
     public void setShowControls(boolean value) {
         mShowControls = value;
     }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
new file mode 100644
index 0000000..debaec7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
@@ -0,0 +1,65 @@
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class ImageSlave extends ImageShow {
+    private ImageShow mMasterImageShow = null;
+
+    public ImageSlave(Context context) {
+        super(context);
+    }
+
+    public ImageSlave(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ImageShow getMaster() {
+        return mMasterImageShow;
+    }
+
+    public void setMaster(ImageShow master) {
+        mMasterImageShow = master;
+    }
+
+    public ImagePreset getImagePreset() {
+        return mMasterImageShow.getImagePreset();
+    }
+
+    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+        mMasterImageShow.setImagePreset(preset, addToHistory);
+    }
+
+    public void setCurrentFilter(ImageFilter filter) {
+        mMasterImageShow.setCurrentFilter(filter);
+    }
+
+    public ImageFilter getCurrentFilter() {
+        return mMasterImageShow.getCurrentFilter();
+    }
+
+    public void updateAngle() {
+        mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor);
+    }
+
+    public boolean showTitle() {
+        return false;
+    }
+
+    public float getImageRotation() {
+        return mMasterImageShow.getImageRotation();
+    }
+
+    public float getImageRotationZoomFactor() {
+        return mMasterImageShow.getImageRotationZoomFactor();
+    }
+
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
index c4f0456..eb034bf 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -15,8 +15,7 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
-public class ImageStraighten extends ImageShow {
-    private ImageShow mMasterImageShow = null;
+public class ImageStraighten extends ImageSlave {
     private float mImageRotation = 0;
     private float mImageRotationZoomFactor = 0;
 
@@ -48,26 +47,6 @@
         super(context, attrs);
     }
 
-    public void updateAngle() {
-        mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor);
-    }
-
-    public void setMaster(ImageShow master) {
-        mMasterImageShow = master;
-    }
-
-    public boolean showTitle() {
-        return false;
-    }
-
-    public ImagePreset getImagePreset() {
-        return mMasterImageShow.getImagePreset();
-    }
-
-    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
-        mMasterImageShow.setImagePreset(preset, addToHistory);
-    }
-
     // ///////////////////////////////////////////////////////////////////////////
     // touch event handler
 
@@ -168,7 +147,7 @@
         // so that we can fake the rotation, etc.
         Bitmap image = null; // mMasterImageShow.mFilteredImage;
         if (image == null) {
-            image = mMasterImageShow.mForegroundImage;
+            image = getMaster().mForegroundImage;
         }
         if (image == null) {
             return;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
new file mode 100644
index 0000000..1c5a9b5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
@@ -0,0 +1,134 @@
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+
+public class ImageZoom extends ImageSlave implements OnGestureListener, OnDoubleTapListener {
+    private boolean mTouchDown = false;
+    private boolean mZoomedIn = false;
+    private Rect mZoomBounds = null;
+    private GestureDetector mGestureDetector = null;
+
+    public ImageZoom(Context context) {
+        super(context);
+        setupGestureDetector(context);
+    }
+
+    public ImageZoom(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setupGestureDetector(context);
+    }
+
+    public void setupGestureDetector(Context context) {
+        mGestureDetector = new GestureDetector(context, this);
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean ret = mGestureDetector.onTouchEvent(event);
+        ret = super.onTouchEvent(event);
+        return ret;
+    }
+
+    public void onTouchDown(float x, float y) {
+        super.onTouchDown(x, y);
+        if (mZoomedIn || mTouchDown) {
+            return;
+        }
+        mTouchDown = true;
+        Rect originalBounds = mImageLoader.getOriginalBounds();
+        Rect imageBounds = getImageBounds();
+        float touchX = x - imageBounds.left;
+        float touchY = y - imageBounds.top;
+
+        float w = originalBounds.width();
+        float h = originalBounds.height();
+        float ratio = w / h;
+        int mw = getWidth() / 2;
+        int mh = getHeight() / 2;
+        int cx = (int) (w / 2);
+        int cy = (int) (h / 2);
+        cx = (int) (touchX / imageBounds.width() * w);
+        cy = (int) (touchY / imageBounds.height() * h);
+        int left = cx - mw;
+        int top = cy - mh;
+        mZoomBounds = new Rect(left, top, left + mw * 2, top + mh * 2);
+    }
+
+    public void onTouchUp() {
+        mTouchDown = false;
+    }
+
+    public void onDraw(Canvas canvas) {
+        drawBackground(canvas);
+        Bitmap filteredImage = null;
+        if ((mZoomedIn ||mTouchDown) && mImageLoader != null) {
+            filteredImage = mImageLoader.getScaleOneImageForPreset(this, getImagePreset(), mZoomBounds, false);
+        } else {
+            getFilteredImage();
+            filteredImage = mFilteredImage;
+        }
+        drawImage(canvas, filteredImage);
+        if (showControls()) {
+            mSliderController.onDraw(canvas);
+        }
+
+        drawToast(canvas);
+    }
+
+    // TODO: move back some of that touch handling to a superclass / refactor
+    // SlideController into a more generic gesture detector
+    @Override
+    public boolean onDown(MotionEvent arg0) {
+        return false;
+    }
+
+    @Override
+    public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent arg0) {
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
+        return false;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent arg0) {
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent arg0) {
+        return false;
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent arg0) {
+        return false;
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent arg0) {
+        mZoomedIn = !mZoomedIn;
+        invalidate();
+        return false;
+    }
+
+    @Override
+    public boolean onDoubleTapEvent(MotionEvent arg0) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
index 8091c1e..3da058b 100644
--- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -4,6 +4,7 @@
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.ImageSlave;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 import com.android.gallery3d.filtershow.ui.ControlPoint;
 import com.android.gallery3d.filtershow.ui.Spline;
@@ -22,7 +23,7 @@
 import android.widget.PopupMenu;
 import android.widget.Toast;
 
-public class ImageCurves extends ImageShow {
+public class ImageCurves extends ImageSlave {
 
     private static final String LOGTAG = "ImageCurves";
     Paint gPaint = new Paint();
@@ -31,7 +32,6 @@
     float[] mAppliedCurve = new float[256];
     private boolean mDidAddPoint = false;
     private boolean mDidDelete = false;
-    private ImageShow mMasterImageShow = null;
     private ControlPoint mCurrentControlPoint = null;
     private boolean mUseRed = true;
     private boolean mUseGreen = true;
@@ -47,10 +47,6 @@
         resetCurve();
     }
 
-    public void setMaster(ImageShow master) {
-        mMasterImageShow = master;
-    }
-
     public boolean showTitle() {
         return false;
     }
@@ -68,7 +64,7 @@
     }
 
     public void reloadCurve() {
-        if (mMasterImageShow != null) {
+        if (getMaster() != null) {
             String filterName = getFilterName();
             ImageFilterCurves filter = (ImageFilterCurves) getImagePreset()
                     .getFilter(filterName);
@@ -86,27 +82,11 @@
 
         mSpline.addPoint(0.0f, 1.0f);
         mSpline.addPoint(1.0f, 0.0f);
-        if (mMasterImageShow != null) {
+        if (getMaster() != null) {
             applyNewCurve();
         }
     }
 
-    public ImagePreset getImagePreset() {
-        return mMasterImageShow.getImagePreset();
-    }
-
-    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
-        mMasterImageShow.setImagePreset(preset, addToHistory);
-    }
-
-    public float getImageRotation() {
-        return mMasterImageShow.getImageRotation();
-    }
-
-    public float getImageRotationZoomFactor() {
-        return mMasterImageShow.getImageRotationZoomFactor();
-    }
-
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderController.java b/src/com/android/gallery3d/filtershow/ui/SliderController.java
index b5b9c81..f2f0df3 100644
--- a/src/com/android/gallery3d/filtershow/ui/SliderController.java
+++ b/src/com/android/gallery3d/filtershow/ui/SliderController.java
@@ -116,6 +116,9 @@
         mCurrentX = x;
         mCurrentY = y;
         mMode = MODES.DOWN;
+        if (mListener != null) {
+            mListener.onTouchDown(x, y);
+        }
     }
 
     public void setActionMove(float x, float y) {
@@ -131,6 +134,9 @@
     public void setActionUp() {
         mMode = MODES.UP;
         mOriginalValue = computeValue();
+        if (mListener != null) {
+            mListener.onTouchUp();
+        }
     }
 
     public void setNoAction() {
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderListener.java b/src/com/android/gallery3d/filtershow/ui/SliderListener.java
index 67e28de..b11966e 100644
--- a/src/com/android/gallery3d/filtershow/ui/SliderListener.java
+++ b/src/com/android/gallery3d/filtershow/ui/SliderListener.java
@@ -3,4 +3,6 @@
 
 public interface SliderListener {
     public void onNewValue(int value);
+    public void onTouchDown(float x, float y);
+    public void onTouchUp();
 }
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index a5a15bf..a7357e0 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -1259,10 +1259,11 @@
                 mIgnoreUpEvent = false;
                 return;
             }
-            if (!mFilmMode || mHadFling) {
-                snapback();
-            } else {
+
+            if (mFilmMode && !mHadFling && mFirstScrollX) {
                 snapToNeighborImage();
+            } else {
+                snapback();
             }
         }
 
diff --git a/src/com/android/gallery3d/ui/SlideshowView.java b/src/com/android/gallery3d/ui/SlideshowView.java
index a057bb7..bb36c47 100644
--- a/src/com/android/gallery3d/ui/SlideshowView.java
+++ b/src/com/android/gallery3d/ui/SlideshowView.java
@@ -142,8 +142,8 @@
             int viewWidth = getWidth();
             int viewHeight = getHeight();
 
-            float initScale = Math.min(2f, Math.min((float)
-                    viewWidth / mWidth, (float) viewHeight / mHeight));
+            float initScale = Math.min((float)
+                    viewWidth / mWidth, (float) viewHeight / mHeight);
             float scale = initScale * (1 + SCALE_SPEED * mProgress);
 
             float centerX = viewWidth / 2 + mMovingVector.x * mProgress;
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 9eb4f47..5ce06be 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -29,6 +29,7 @@
 import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DecodeUtils;
 import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -43,15 +44,12 @@
 
     // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the
     // texture to avoid seams between tiles.
-    private static final int TILE_SIZE = 254;
+    private static int TILE_SIZE;
     private static final int TILE_BORDER = 1;
-    private static final int BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2;
+    private static int BITMAP_SIZE;
     private static final int UPLOAD_LIMIT = 1;
 
-    private static final BitmapPool sTilePool =
-            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
-            ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128)
-            : null;
+    private static BitmapPool sTilePool;
 
     /*
      *  This is the tile state in the CPU side.
@@ -152,6 +150,18 @@
     public TileImageView(GalleryContext context) {
         mThreadPool = context.getThreadPool();
         mTileDecoder = mThreadPool.submit(new TileDecoder());
+        if (TILE_SIZE == 0) {
+            if (GalleryUtils.isHighResolution(context.getAndroidContext())) {
+                TILE_SIZE = 510 ;
+            } else {
+                TILE_SIZE = 254;
+            }
+            BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2;
+            sTilePool =
+                    ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
+                    ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128)
+                    : null;
+        }
     }
 
     public void setModel(Model model) {
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index a14df75..08d3379 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -112,7 +112,6 @@
     @Override
     public Bitmap getTile(int level, int x, int y, int tileSize,
             int borderSize, BitmapPool pool) {
-
         if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
             return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize);
         }
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index a3eccb8..efa0614 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -87,6 +87,14 @@
                 R.color.bitmap_screennail_placeholder));
     }
 
+    public static boolean isHighResolution(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics.heightPixels > 2048 ||  metrics.widthPixels > 2048;
+    }
+
     public static float[] intColorToFloatARGBArray(int from) {
         return new float[] {
             Color.alpha(from) / 255f,
diff --git a/src_pd/com/android/gallery3d/app/StitchingProgressManager.java b/src_pd/com/android/gallery3d/app/StitchingProgressManager.java
new file mode 100644
index 0000000..d0e2a69
--- /dev/null
+++ b/src_pd/com/android/gallery3d/app/StitchingProgressManager.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.app;
+
+import com.android.gallery3d.data.MediaItem;
+
+import java.util.ArrayList;
+
+public class StitchingProgressManager {
+    public void addChangeListener(StitchingChangeListener l) {
+    }
+
+    public void removeChangeListener(StitchingChangeListener l) {
+    }
+
+    public int getItemCount() {
+        return 0;
+    }
+
+    public MediaItem getItem(int i) {
+        return null;
+    }
+
+    public ArrayList<MediaItem> getAllItems() {
+        return null;
+    }
+
+    public int getProgress(String filePath) {
+        return 100;
+    }
+}
diff --git a/src_pd/com/android/gallery3d/util/LightCycleHelper.java b/src_pd/com/android/gallery3d/util/LightCycleHelper.java
index dcedc2d..a4da43c 100644
--- a/src_pd/com/android/gallery3d/util/LightCycleHelper.java
+++ b/src_pd/com/android/gallery3d/util/LightCycleHelper.java
@@ -23,6 +23,9 @@
 import android.net.Uri;
 
 import com.android.camera.CameraModule;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.app.StitchingProgressManager;
+import com.android.gallery3d.data.MediaSource;
 
 public class LightCycleHelper {
 
@@ -30,11 +33,11 @@
         /* Do nothing */
     }
 
-    public static synchronized boolean hasLightCycleView(Context context) {
+    public static boolean hasLightCycleView(Context context) {
         return false;
     }
 
-    public static synchronized boolean hasLightCycleCapture(Context context) {
+    public static boolean hasLightCycleCapture(Context context) {
         return false;
     }
 
@@ -49,4 +52,16 @@
     public static CameraModule createPanoramaModule() {
         return null;
     }
+
+    public static StitchingProgressManager createStitchingManagerInstance(GalleryApp app) {
+        return null;
+    }
+
+    public static MediaSource createMediaSourceInstance(GalleryApp app) {
+        return null;
+    }
+
+    public static String wrapGalleryPath(String path) {
+        return path;
+    }
 }
diff --git a/tests/src/com/android/gallery3d/data/GalleryAppStub.java b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
index 47693d2..5aff2a2 100644
--- a/tests/src/com/android/gallery3d/data/GalleryAppStub.java
+++ b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
@@ -18,6 +18,7 @@
 
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.app.StateManager;
+import com.android.gallery3d.app.StitchingProgressManager;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.util.ThreadPool;
 
@@ -42,4 +43,5 @@
     public ContentResolver getContentResolver() { return null; }
     public ThreadPool getThreadPool() { return null; }
     public DownloadCache getDownloadCache() { return null; }
+    public StitchingProgressManager getStitchingProgressManager() { return null; }
 }