Adding PhotoEditor & quake res/; Misc fixes

Change-Id: I3d96725b2c6c96eabddc484a62a1919ac20814be
diff --git a/platforms/android-portable/arch-llvm/usr/README b/platforms/android-portable/arch-llvm/usr/README
new file mode 100644
index 0000000..f634f09
--- /dev/null
+++ b/platforms/android-portable/arch-llvm/usr/README
@@ -0,0 +1,5 @@
+This file is a placeholder that reminds us
+this directory should put the portable headers.
+Currently a link is poinitng to ndk/platform
+
+This README should be removed in future.
diff --git a/platforms/android-portable/arch-llvm/usr/include b/platforms/android-portable/arch-llvm/usr/include
new file mode 120000
index 0000000..914bdcf
--- /dev/null
+++ b/platforms/android-portable/arch-llvm/usr/include
@@ -0,0 +1 @@
+../../../../../ndk/platforms/android-9/arch-arm/usr/include
\ No newline at end of file
diff --git a/samples/PhotoEditor/Android.mk b/samples/PhotoEditor/Android.mk
new file mode 100644
index 0000000..87acd60
--- /dev/null
+++ b/samples/PhotoEditor/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := PhotoEditor
+
+LOCAL_JNI_SHARED_LIBRARIES := libjni_photoeditor
+
+LOCAL_REQUIRED_MODULES := libjni_photoeditor
+
+LOCAL_SDK_VERSION := 11
+
+include $(BUILD_PACKAGE)
+
+ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)
+include $(call all-makefiles-under, $(LOCAL_PATH))
+endif
diff --git a/samples/PhotoEditor/AndroidManifest.xml b/samples/PhotoEditor/AndroidManifest.xml
new file mode 100644
index 0000000..f70bbd6
--- /dev/null
+++ b/samples/PhotoEditor/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest package="com.android.photoeditor"
+        android:versionCode="10001"
+        android:versionName="1.0.001" xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <original-package android:name="com.android.photoeditor" />
+
+    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="11" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+            android:icon="@drawable/icon"
+            android:label="@string/app_name"
+            android:largeHeap="true"
+            android:hardwareAccelerated="true">
+        <activity android:name=".PhotoEditor"
+                android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
+                android:clearTaskOnLaunch="true"
+                android:configChanges="orientation"
+                android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.EDIT" />
+                <data android:mimeType="image/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/PhotoEditor/jni/Android-portable.mk b/samples/PhotoEditor/jni/Android-portable.mk
new file mode 100644
index 0000000..861ff01
--- /dev/null
+++ b/samples/PhotoEditor/jni/Android-portable.mk
@@ -0,0 +1,46 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+#LOCAL_SHARED_LIBRARIES := libm liblog libjnigraphics
+#LOCAL_LDLIBS := -lm -llog -ljnigraphics -lbcc
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libjni_photoeditor_portable
+
+LOCAL_SRC_FILES := _jni.cpp \
+    utils.cpp \
+    backlight.cpp \
+    blur.cpp \
+    colortemp.cpp \
+    convolution.cpp \
+    copy.cpp \
+    crossprocess.cpp \
+    duotone.cpp \
+    fisheye.cpp \
+    flip.cpp \
+    grain.cpp \
+    grayscale.cpp \
+    heq.cpp \
+    negative.cpp \
+    quantize.cpp \
+    redeye.cpp \
+    saturate.cpp \
+    sepia.cpp \
+    sharpen.cpp \
+    tint.cpp \
+    vignetting.cpp \
+    warmify.cpp \
+    whiteblack.cpp
+
+# This doesn't work on non-ARM yet.
+ifeq ($(TARGET_ARCH), arm)
+    LOCAL_NDK_VERSION := 5
+    LOCAL_SDK_VERSION := 9
+endif
+
+LOCAL_C_INCLUDES := $(OUT)/../../../../frameworks/compile/libbcc/include
+#LOCAL_LDFLAGS := -L$(OUT)/system/lib
+
+include $(BUILD_BITCODE)
diff --git a/samples/PhotoEditor/jni/Android.mk b/samples/PhotoEditor/jni/Android.mk
new file mode 100644
index 0000000..c94794a
--- /dev/null
+++ b/samples/PhotoEditor/jni/Android.mk
@@ -0,0 +1,49 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+#LOCAL_SHARED_LIBRARIES := libm liblog libjnigraphics
+LOCAL_LDLIBS := -lm -llog -ljnigraphics -lbcc
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libjni_photoeditor
+
+LOCAL_SRC_FILES := _jni.cpp \
+    utils.cpp \
+    backlight.cpp \
+    blur.cpp \
+    colortemp.cpp \
+    convolution.cpp \
+    copy.cpp \
+    crossprocess.cpp \
+    duotone.cpp \
+    fisheye.cpp \
+    flip.cpp \
+    grain.cpp \
+    grayscale.cpp \
+    heq.cpp \
+    negative.cpp \
+    quantize.cpp \
+    redeye.cpp \
+    saturate.cpp \
+    sepia.cpp \
+    sharpen.cpp \
+    tint.cpp \
+    vignetting.cpp \
+    warmify.cpp \
+    whiteblack.cpp
+
+# This doesn't work on non-ARM yet.
+ifeq ($(TARGET_ARCH), arm)
+    LOCAL_NDK_VERSION := 5
+    LOCAL_SDK_VERSION := 9
+endif
+
+LOCAL_CFLAGS := -Werror \
+    -I$(OUT)/../../../../frameworks/compile/libbcc/include \
+    -I$(OUT)/../../../../dalvik/libnativehelper/include 
+
+LOCAL_LDFLAGS := -L$(OUT)/system/lib
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/samples/PhotoEditor/jni/Application.mk b/samples/PhotoEditor/jni/Application.mk
new file mode 100644
index 0000000..f05229c
--- /dev/null
+++ b/samples/PhotoEditor/jni/Application.mk
@@ -0,0 +1 @@
+APP_ABI := armeabi armeabi-v7a
diff --git a/samples/PhotoEditor/jni/_jni.cpp b/samples/PhotoEditor/jni/_jni.cpp
new file mode 100644
index 0000000..87263ff
--- /dev/null
+++ b/samples/PhotoEditor/jni/_jni.cpp
@@ -0,0 +1,87 @@
+#if !defined(__clang__)
+
+#include <nativehelper/jni.h>
+#include <android/log.h>
+#include <bcc/bcc.h>
+#include <dlfcn.h>
+
+
+#define LOG_TAG "libjni_photoeditor"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+#include "_jni.h"
+
+#define DEFINE(f) { #f, 0 },
+JNIFuncType JNIFunc[JNI_max] =
+{
+#include "_jnif.h"
+};
+#undef DEFINE
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    LOGI("JNI_OnLoad\n");
+   
+#define DEFINE(f) JNIFunc[ JNI_ ## f ].func_ptr = (void *)f;
+   #include "_jnif.h"
+#undef DEFINE   
+
+    return JNI_VERSION_1_4;
+}
+
+static void* lookupSymbol(void* pContext, const char* name)
+{
+    return (void*) dlsym(RTLD_DEFAULT, name);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_com_android_photoeditor_filters_ImageUtils_init(
+    JNIEnv *env, jobject obj, jbyteArray scriptRef, jint length)
+{   
+    void *new_func_ptr[JNI_max];
+    int i, all_func_found = 1;
+
+    BCCScriptRef script_ref = bccCreateScript();
+    jbyte* script_ptr = (jbyte *)env->GetPrimitiveArrayCritical(scriptRef, (jboolean *)0);
+    LOGI("BCC Script Len: %d", length);
+    if (bccReadBC(script_ref, "libjni_photoeditor_portable.bc", (const char*)script_ptr, length, 0)) {
+        LOGE("Error! Cannot bccReadBc");
+        return JNI_FALSE;
+    }
+    if (script_ptr) {
+        env->ReleasePrimitiveArrayCritical(scriptRef, script_ptr, 0);
+    }
+  #if 0
+    if (bccLinkFile(script_ref, "/system/lib/libclcore.bc", 0)) {
+        LOGE("Error! Cannot bccLinkBC");
+        return JNI_FALSE;
+    }
+  #endif
+    bccRegisterSymbolCallback(script_ref, lookupSymbol, NULL);
+    if (bccPrepareExecutableEx(script_ref, ".", "/data/data/com.android.photoeditor/photoeditorLLVM", 0)) {
+        LOGE("Error! Cannot bccPrepareExecutableEx");
+        return JNI_FALSE;
+    }
+    for(i=0; i<JNI_max; i++) {
+        new_func_ptr[i] = bccGetFuncAddr(script_ref, JNIFunc[i].func_name);
+        if (new_func_ptr[i] == NULL) {
+	    LOGE("Error! Cannot find %s()\n", JNIFunc[i].func_name);
+	    all_func_found = 0;
+          //return JNI_FALSE;
+	} else 
+            LOGI("Found %s() @ 0x%x", JNIFunc[i].func_name, (unsigned)new_func_ptr[i]);
+    }
+
+   //bccDisposeScript(script_ref);
+
+    if (all_func_found)
+    {
+        LOGI("Use LLVM version");
+        for(i=0; i<JNI_max; i++)
+	     JNIFunc[i].func_ptr = new_func_ptr[i];
+    }
+
+    return JNI_TRUE;
+}
+
+#endif // __clang__
\ No newline at end of file
diff --git a/samples/PhotoEditor/jni/_jni.h b/samples/PhotoEditor/jni/_jni.h
new file mode 100644
index 0000000..b806a15
--- /dev/null
+++ b/samples/PhotoEditor/jni/_jni.h
@@ -0,0 +1,69 @@
+extern "C" 
+{
+   void Backlight(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat backlight);
+   void Blur(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void ColorTemp(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void Copy(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void CrossProcess(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void Duotone(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint color1, jint color2);
+   void Fisheye(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat focus_x, jfloat focus_y, jfloat scale);
+   void FlipHorizontal(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void FlipVertical(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void FlipBoth(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void Grain(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat noise_scale);
+   void Grayscale(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void HEQ(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void Negative(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void Quantize(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void RedEye(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jobjectArray redeye_positions, jfloat radius, jfloat intensity);
+   void Saturation(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void Sepia(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void Sharpen(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+   void Tint(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint tint);
+   void Vignetting(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat range);
+   void Warmify(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+   void WhiteBlack(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat white, jfloat black);
+};
+   
+typedef void (*pBacklightType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat backlight);
+typedef void (*pBlurType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pColorTempType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pCopyType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pCrossProcessType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pDuotoneType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint color1, jint color2);
+typedef void (*pFisheyeType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat focus_x, jfloat focus_y, jfloat scale);
+typedef void (*pFlipHorizontalType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pFlipVerticalType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pFlipBothType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pGrainType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat noise_scale);
+typedef void (*pGrayscaleType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pHEQType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pNegativeType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pQuantizeType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pRedEyeType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jobjectArray redeye_positions, jfloat radius, jfloat intensity);
+typedef void (*pSaturationType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pSepiaType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pSharpenType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale);
+typedef void (*pTintType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint tint);
+typedef void (*pVignettingType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat range);
+typedef void (*pWarmifyType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap);
+typedef void (*pWhiteBlackType)(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat white, jfloat black);
+
+#define DEFINE(f) JNI_ ## f,
+enum
+{
+#include "_jnif.h"
+   JNI_max
+};
+#undef DEFINE
+
+
+typedef struct 
+{
+   const char *func_name;
+   void *func_ptr;
+} JNIFuncType;
+
+extern JNIFuncType JNIFunc[JNI_max];
+
+
diff --git a/samples/PhotoEditor/jni/_jnif.h b/samples/PhotoEditor/jni/_jnif.h
new file mode 100644
index 0000000..da3bf51
--- /dev/null
+++ b/samples/PhotoEditor/jni/_jnif.h
@@ -0,0 +1,23 @@
+DEFINE(Backlight)
+DEFINE(Blur)
+DEFINE(ColorTemp)
+DEFINE(Copy)
+DEFINE(CrossProcess)
+DEFINE(Duotone)
+DEFINE(Fisheye)
+DEFINE(FlipHorizontal)
+DEFINE(FlipVertical)
+DEFINE(FlipBoth)
+DEFINE(Grain)
+DEFINE(Grayscale)
+DEFINE(HEQ)
+DEFINE(Negative)
+DEFINE(Quantize)
+DEFINE(RedEye)
+DEFINE(Saturation)
+DEFINE(Sepia)
+DEFINE(Sharpen)
+DEFINE(Tint)
+DEFINE(Vignetting)
+DEFINE(Warmify)
+DEFINE(WhiteBlack)
diff --git a/samples/PhotoEditor/jni/backlight.cpp b/samples/PhotoEditor/jni/backlight.cpp
new file mode 100644
index 0000000..7beeecb
--- /dev/null
+++ b/samples/PhotoEditor/jni/backlight.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeBacklight(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat backlight) {
+   pBacklightType f = (pBacklightType)JNIFunc[JNI_Backlight].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, backlight);
+}
+
+extern "C" void Backlight(JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat backlight) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Backlight failed, error=%d", ret);
+    return;
+  }
+
+  const float fade_gamma = 0.3f;
+  const float amt = 1.0f - backlight;
+  const float mult = 1.0f / (amt * 0.7f + 0.3f);
+  const float faded = fade_gamma + (1.0f - fade_gamma) * mult;
+  const float igamma = 1.0f / faded;
+
+  int lookup[256];
+  for (int i = 0; i < 256; i++) {
+    float value = static_cast<float>(pow(mult * i / 255.0f, igamma));
+    if (value > 256.0f) {
+      value = 256.0f;
+    }
+    lookup[i] = floor(255.0f * value) - i;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      int32_t src_red = src->rgba8[0];
+      int32_t src_green = src->rgba8[1];
+      int32_t src_blue = src->rgba8[2];
+      int32_t src_alpha = src->rgba8[3];
+
+      int32_t lightmask = (src_red + src_green * 2 + src_blue) >> 2;
+      int32_t backmask = 256 * (255 - lightmask);
+
+      int32_t diff_red = lookup[src_red] * backmask;
+      int32_t diff_green = lookup[src_green] * backmask;
+      int32_t diff_blue = lookup[src_blue] * backmask;
+
+      int32_t dst_red = src_red + (diff_red >> 16);
+      int32_t dst_green = src_green + (diff_green >> 16);
+      int32_t dst_blue = src_blue + (diff_blue >> 16);
+
+      if (dst_red > 255) {
+        dst_red = 255;
+      }
+      if (dst_green > 255) {
+        dst_green = 255;
+      }
+      if (dst_blue > 255) {
+        dst_blue = 255;
+      }
+
+      *dst = (src_alpha << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/blur.cpp b/samples/PhotoEditor/jni/blur.cpp
new file mode 100644
index 0000000..1888c35
--- /dev/null
+++ b/samples/PhotoEditor/jni/blur.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "convolution.h"
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::convolution::SpecialConvolution;
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeBlur(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pBlurType f = (pBlurType)JNIFunc[JNI_Blur].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+
+extern "C" void Blur(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Sharpen failed, error=%d", ret);
+    return;
+  }
+
+  /* Divide blur scale by a factor of 5 which comes from trials. Due to a small (3x3)
+   * kernel is used for convolution, small blur factor causes double edge. */
+  SpecialConvolution(&src_info, &dst_info, src_pixels, dst_pixels, scale / 5);
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/colortemp.cpp b/samples/PhotoEditor/jni/colortemp.cpp
new file mode 100644
index 0000000..07a9049
--- /dev/null
+++ b/samples/PhotoEditor/jni/colortemp.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+const int k256Multiply255 = 65280;
+
+/*
+ * @param scale ranges from -0.5 to 0.5.
+ */
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeColorTemp(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pColorTempType f = (pColorTempType)JNIFunc[JNI_ColorTemp].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+   
+extern "C" void ColorTemp(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in ColorTemp failed, error=%d", ret);
+    return;
+  }
+
+  int curve[256];
+  for (int i = 0; i < 256; i++) {
+    curve[i] = i * (256 - i);
+  }
+
+  int ics = static_cast<int>(scale * 256);
+  int icsr = ics;
+  int icsg = ics > 0 ? ics : 0;
+  int icsb = ics;
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      int32_t src_red = src->rgba8[0];
+      int32_t src_green = src->rgba8[1];
+      int32_t src_blue = src->rgba8[2];
+      int32_t src_alpha = src->rgba8[3];
+
+      int32_t dst_red = src_red + ((curve[src_red] * icsr) >> 15);
+      int32_t dst_green = src_green + ((curve[src_green] * icsg) >> 17);
+      int32_t dst_blue = src_blue - ((curve[src_blue] * icsb) >> 15);
+
+      int rgb_max = MAX3(dst_red, dst_green, dst_blue);
+      if (rgb_max > 255) {
+        int invmax = k256Multiply255 / rgb_max;
+        dst_red = dst_red * invmax >> 8;
+        dst_green = dst_green * invmax >> 8;
+        dst_blue = dst_blue * invmax >> 8;
+      }
+
+      *dst = (src_alpha << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/convolution.cpp b/samples/PhotoEditor/jni/convolution.cpp
new file mode 100644
index 0000000..9be537a
--- /dev/null
+++ b/samples/PhotoEditor/jni/convolution.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+
+#include <cstdlib>
+
+#include "utils.h"
+
+using android::apps::photoeditor::utils::clamp;
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace android {
+namespace apps {
+namespace photoeditor {
+namespace convolution {
+
+/*
+ * Used for convolution of following kernel:
+ *   n n n
+ *   n c n
+ *   n n n
+ * n: neighbor, c: center
+ */
+void SpecialConvolution(AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+    void *src_pixels, void *dst_pixels, float neighbor) {
+
+  if (neighbor == 0) {
+    memcpy(dst_pixels, src_pixels, src_info->stride * src_info->height);
+    return;
+  }
+
+  /* Copy the first row to dst_pixels */
+  memcpy(dst_pixels, src_pixels, src_info->stride);
+  src_pixels = reinterpret_cast<char*>(src_pixels) + src_info->stride;
+  dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info->stride;
+
+  const int32_t kShiftBits = 10;
+  int32_t fixed_neighbor = static_cast<int32_t>((1 << kShiftBits) * neighbor);
+  int32_t fixed_center = static_cast<int32_t>((1 << kShiftBits) * (1 - neighbor * 8));
+
+  for (uint32_t scan_line = 1; scan_line < dst_info->height - 1; scan_line++) {
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    uint32_t* src_prev = reinterpret_cast<uint32_t*>
+        (reinterpret_cast<char*>(src_pixels) - src_info->stride);
+    uint32_t* src_next = reinterpret_cast<uint32_t*>
+        (reinterpret_cast<char*>(src_pixels) + src_info->stride);
+
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    *dst++ = src->rgba32;
+    src++;
+    src_prev++;
+    src_next++;
+
+    uint32_t* dst_line_end = dst + dst_info->width - 1;
+    while (dst < dst_line_end) {
+      int32_t src_red = src->rgba8[0];
+      int32_t src_green = src->rgba8[1];
+      int32_t src_blue = src->rgba8[2];
+      int32_t src_alpha = src->rgba8[3];
+
+      uint8_t* n1 = reinterpret_cast<uint8_t*>(src_prev - 1);
+      uint8_t* n2 = reinterpret_cast<uint8_t*>(src - 1);
+      uint8_t* n3 = reinterpret_cast<uint8_t*>(src_next - 1);
+      uint8_t* n4 = reinterpret_cast<uint8_t*>(src_prev);
+      uint8_t* n5 = reinterpret_cast<uint8_t*>(src_next);
+      uint8_t* n6 = reinterpret_cast<uint8_t*>(src_prev + 1);
+      uint8_t* n7 = reinterpret_cast<uint8_t*>(src + 1);
+      uint8_t* n8 = reinterpret_cast<uint8_t*>(src_next + 1);
+
+      int32_t red = n1[0] + n2[0] + n3[0] + n4[0] + n5[0] + n6[0] + n7[0] + n8[0];
+      int32_t green = n1[1] + n2[1] + n3[1] + n4[1] + n5[1] + n6[1] + n7[1] + n8[1];
+      int32_t blue = n1[2] + n2[2] + n3[2] + n4[2] + n5[2] + n6[2] + n7[2] + n8[2];
+
+      red = (fixed_neighbor * red + fixed_center * src_red) >> kShiftBits;
+      green = (fixed_neighbor * green + fixed_center * src_green) >> kShiftBits;
+      blue = (fixed_neighbor * blue + fixed_center * src_blue) >> kShiftBits;
+
+      red = clamp(red, 0, 255);
+      green = clamp(green, 0, 255);
+      blue = clamp(blue, 0, 255);
+
+      *dst = (src_alpha << 24) | (blue << 16) | (green << 8) | red;
+      dst++;
+      src++;
+      src_prev++;
+      src_next++;
+    }
+    *dst = src->rgba32;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info->stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info->stride;
+  }
+
+  /* Copy the last row to dst_pixels */
+  memcpy(dst_pixels, src_pixels, src_info->stride);
+}
+
+}  // namespace convolution
+}  // namespace photoeditor
+}  // namespace apps
+}  // namespace android
diff --git a/samples/PhotoEditor/jni/convolution.h b/samples/PhotoEditor/jni/convolution.h
new file mode 100644
index 0000000..fd6d5f2
--- /dev/null
+++ b/samples/PhotoEditor/jni/convolution.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PHOTOEDITOR_JNI_CONVOLUTION_H_
+#define PHOTOEDITOR_JNI_CONVOLUTION_H_
+
+#include <android/bitmap.h>
+
+namespace android {
+namespace apps {
+namespace photoeditor {
+namespace convolution {
+
+void SpecialConvolution(AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+    void *src_pixels, void *dst_pixels, float neighbor);
+
+}  // namespace convolution
+}  // namespace photoeditor
+}  // namespace apps
+}  // namespace android
+
+#endif  // PHOTOEDITOR_JNI_CONVOLUTION_H_
diff --git a/samples/PhotoEditor/jni/copy.cpp b/samples/PhotoEditor/jni/copy.cpp
new file mode 100644
index 0000000..01707de
--- /dev/null
+++ b/samples/PhotoEditor/jni/copy.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cstdlib>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeCopy(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pCopyType f = (pCopyType)JNIFunc[JNI_Copy].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void Copy(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Copy failed, error=%d", ret);
+    return;
+  }
+
+  memcpy(dst_pixels, src_pixels, src_info.stride * src_info.height);
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/crossprocess.cpp b/samples/PhotoEditor/jni/crossprocess.cpp
new file mode 100644
index 0000000..e150693
--- /dev/null
+++ b/samples/PhotoEditor/jni/crossprocess.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeCrossProcess(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pCrossProcessType f = (pCrossProcessType)JNIFunc[JNI_CrossProcess].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+
+extern "C" void CrossProcess(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Crossprocess failed, error=%d", ret);
+    return;
+  }
+
+  const int k128SquareDouble = 32768;  // 2 * 128^2
+  const int k128CubicDouble = 4194304;  // 2 * 128^3
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      // Enhance red contrast
+      uint32_t r = src->rgba8[0];
+      uint32_t dst_red;
+      if (r <= 128) {
+        dst_red = 255 * r * r * r / k128CubicDouble;
+      } else {
+        uint32_t diff_r = 255 - r;
+        dst_red = 255 - 255 * diff_r * diff_r * diff_r / k128CubicDouble;
+      }
+      // Enhance green contrast
+      int g = src->rgba8[1];
+      uint32_t dst_green;
+      if (g <= 128) {
+        dst_green = 255 * g * g / k128SquareDouble;
+      } else {
+        int diff_g = 255 - g;
+        dst_green = 255 - 255 * diff_g * diff_g / k128SquareDouble;
+      }
+      // Narrow the blue channel, from 64 to 192.
+      int dst_blue = src->rgba8[2] * 128 / 255 + 64;
+
+      *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/duotone.cpp b/samples/PhotoEditor/jni/duotone.cpp
new file mode 100644
index 0000000..daae4de
--- /dev/null
+++ b/samples/PhotoEditor/jni/duotone.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeDuotone(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint color1, jint color2) {
+   pDuotoneType f = (pDuotoneType)JNIFunc[JNI_Duotone].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, color1, color2);
+}
+   
+extern "C" void Duotone(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint color1, jint color2) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Duotone failed, error=%d", ret);
+    return;
+  }
+
+  uint8_t red1 = static_cast<uint8_t>(color1 >> 16) & 0xff;
+  uint8_t green1 = static_cast<uint8_t>(color1 >> 8) & 0xff;
+  uint8_t blue1 = static_cast<uint8_t>(color1) & 0xff;
+
+  uint8_t red2 = (uint8_t)(color2 >> 16) & 0xff;
+  uint8_t green2 = (uint8_t)(color2 >> 8) & 0xff;
+  uint8_t blue2 = (uint8_t)color2 & 0xff;
+
+  int r_delta = red2 - red1;
+  int g_delta = green2 - green1;
+  int b_delta = blue2 - blue1;
+
+  const uint32_t kEnergyLevels = 255 * 3 + 1;
+  uint8_t r_table[kEnergyLevels];
+  uint8_t g_table[kEnergyLevels];
+  uint8_t b_table[kEnergyLevels];
+  for (uint32_t energy = 0; energy < kEnergyLevels; energy++) {
+    r_table[energy] = (uint8_t)(red1 + energy * r_delta / kEnergyLevels);
+    g_table[energy] = (uint8_t)(green1 + energy * g_delta / kEnergyLevels);
+    b_table[energy] = (uint8_t)(blue1 + energy * b_delta / kEnergyLevels);
+  }
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      uint32_t energy = src->rgba8[0] + src->rgba8[1] + src->rgba8[2];
+
+      int dst_red = r_table[energy];
+      int dst_green = g_table[energy];
+      int dst_blue = b_table[energy];
+
+      *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/fisheye.cpp b/samples/PhotoEditor/jni/fisheye.cpp
new file mode 100644
index 0000000..5e26a3a
--- /dev/null
+++ b/samples/PhotoEditor/jni/fisheye.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+uint32_t BilinearPixelInterpolation(
+    float x, float y, uint8_t *p00, uint8_t *p01, uint8_t *p10, uint8_t *p11) {
+
+  float coef4 = x * y;
+  float coef2 = x - coef4; // x * (1 - y)
+  float coef3 = y - coef4; // (1 - x) * y
+  float coef1 = 1 - x - coef3; // (1 - x) * (1 - y)
+
+  int dst_red = p00[0] * coef1 + p10[0] * coef2 + p01[0] * coef3 + p11[0] * coef4;
+  int dst_green = p00[1] * coef1 + p10[1] * coef2 + p01[1] * coef3 + p11[1] * coef4;
+  int dst_blue = p00[2] * coef1 + p10[2] * coef2 + p01[2] * coef3 + p11[2] * coef4;
+  if (dst_red > 255) {
+    dst_red = 255;
+  }
+  if (dst_green > 255) {
+    dst_green = 255;
+  }
+  if (dst_blue > 255) {
+    dst_blue = 255;
+  }
+
+  // alpha is not calculated, directly from any point should do.
+  return p00[3] << 24 | (dst_blue << 16) | (dst_green << 8) | dst_red;
+}
+
+void FisheyeMapPixels(
+    float px, float py, uint32_t x, uint32_t y, AndroidBitmapInfo *src_info,
+    AndroidBitmapInfo *dst_info, void *src_pixels, void *dst_pixels) {
+  if (x >= src_info->width || y >= src_info->height) {
+    return;
+  }
+
+  uint32_t px_floor = floor(px);
+  uint32_t py_floor = floor(py);
+
+  uint32_t *p00 = (uint32_t*)((char*)src_pixels + src_info->stride * py_floor) + px_floor;
+  uint32_t *p01, *p10, *p11;
+  if (py_floor + 1 < src_info->height) {
+    p01 = (uint32_t*)((char*)p00 + src_info->stride);
+  } else {
+    p01 = p00;
+  }
+  if (py_floor + 1 < src_info->width) {
+    p10 = p00 + 1;
+    p11 = p01 + 1;
+  } else {
+    p10 = p00;
+    p11 = p01;
+  }
+
+  uint32_t *dst = (uint32_t*)((char*)dst_pixels + dst_info->stride * y) + x;
+  *dst = BilinearPixelInterpolation(px - px_floor, py - py_floor,
+                                    (uint8_t*)p00, (uint8_t*)p01, (uint8_t*)p10, (uint8_t*)p11);
+}
+
+/*
+ * @param scale ranges from 0.0 to 1.0.
+ * @param focus_x is the X-coord of the center of the projection ranging from 0 to 1.
+ * @param focus_y is the Y-coord of the center of the projection ranging from 0 to 1.
+ */
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeFisheye(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap,
+    jfloat focus_x, jfloat focus_y, jfloat scale) {
+   pFisheyeType f = (pFisheyeType)JNIFunc[JNI_Fisheye].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, focus_x, focus_y, scale);
+}
+
+extern "C" void Fisheye(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap,
+    jfloat focus_x, jfloat focus_y, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in FishEye failed, error=%d", ret);
+    return;
+  }
+
+  if (scale == 0) {
+    memcpy(dst_pixels, src_pixels, src_info.stride * src_info.height);
+    return;
+  }
+
+  float far_point = hypotf(src_info.height * focus_y, src_info.width * focus_x);
+  const float r = far_point * 1.15; // radius
+  const float r2 = r * r;
+  const uint32_t center_x = round(src_info.width * focus_x);
+  const uint32_t center_y = round(src_info.height * focus_y);
+  float alpha = 0.75 + scale * 2.0;
+  float linear_scale = far_point /
+      (M_PI_2 - atan(alpha * sqrtf(r2 - far_point * far_point) / far_point));
+
+  for (uint32_t scan_line = 0; scan_line <= center_y; scan_line++) {
+
+    int y = scan_line - center_y;
+    int y2 = y * y;
+
+    for (uint32_t dst_x = 0; dst_x <= center_x; dst_x++) {
+      int x = dst_x - center_x;
+      float xy2 = (x * x + y2);
+      float s_xy2 = sqrtf(xy2);
+      float r_scale = linear_scale * (M_PI_2 - atan(alpha * sqrtf(r2 - xy2) / s_xy2)) / s_xy2;
+      float scaled_x = x * r_scale;
+      float scaled_y = y * r_scale;
+      int nx = center_x - x;
+      int ny = center_y - y;
+      float fpx = center_x + scaled_x;
+      float fnx = center_x - scaled_x;
+      float fpy = center_y + scaled_y;
+      float fny = center_y - scaled_y;
+
+      FisheyeMapPixels(fpx, fpy, dst_x, scan_line, &src_info, &dst_info, src_pixels, dst_pixels);
+      FisheyeMapPixels(fpx, fny, dst_x, ny, &src_info, &dst_info, src_pixels, dst_pixels);
+      FisheyeMapPixels(fnx, fpy, nx, scan_line, &src_info, &dst_info, src_pixels, dst_pixels);
+      FisheyeMapPixels(fnx, fny, nx, ny, &src_info, &dst_info, src_pixels, dst_pixels);
+    }
+  }
+  // Deal with the condition when x = 0 and y = 0 (dst_x = center_x and
+  // scan_line = center_y.
+  uint32_t *dst = (uint32_t*)((char*)dst_pixels + dst_info.stride * center_y) + center_x;
+  *dst = *((uint32_t*)((char*)src_pixels + src_info.stride * center_y) + center_x);
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/flip.cpp b/samples/PhotoEditor/jni/flip.cpp
new file mode 100644
index 0000000..cdeb6d9
--- /dev/null
+++ b/samples/PhotoEditor/jni/flip.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeFlipHorizontal(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pFlipHorizontalType f = (pFlipHorizontalType)JNIFunc[JNI_FlipHorizontal].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+
+extern "C" void FlipHorizontal(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in FlipHorizontal failed, error=%d", ret);
+    return;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels) + src_info.width - 1;
+    uint32_t* src = reinterpret_cast<uint32_t*>(src_pixels);
+    uint32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      *dst = *src;
+      src++;
+      dst--;
+    }
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeFlipVertical(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pFlipVerticalType f = (pFlipVerticalType)JNIFunc[JNI_FlipVertical].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void FlipVertical(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in FlipVertical failed, error=%d", ret);
+    return;
+  }
+
+  dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride * (dst_info.height - 1);
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    uint32_t* src = reinterpret_cast<uint32_t*>(src_pixels);
+    uint32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      *dst = *src;
+      src++;
+      dst++;
+    }
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) - dst_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeFlipBoth(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pFlipBothType f = (pFlipBothType)JNIFunc[JNI_FlipBoth].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void FlipBoth(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in FlipBoth failed, error=%d", ret);
+    return;
+  }
+
+  dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride * (dst_info.height - 1);
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels) + src_info.width - 1;
+    uint32_t* src = reinterpret_cast<uint32_t*>(src_pixels);
+    uint32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      *dst = *src;
+      src++;
+      dst--;
+    }
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) - dst_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/grain.cpp b/samples/PhotoEditor/jni/grain.cpp
new file mode 100644
index 0000000..b051660
--- /dev/null
+++ b/samples/PhotoEditor/jni/grain.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+const uint32_t kShiftBits = 10;
+const uint32_t kShiftValue = (1 << kShiftBits);
+
+/*
+ * Convolution matrix of distance 2 with fixed point of 'kShiftBits' bits
+ * shifted. Thus the sum of this matrix should be 'kShiftValue'. Entries of
+ * small values are not calculated to gain efficiency.
+ * The order ot pixels represented in this matrix is:
+ *  1  2  3
+ *  4  0  5
+ *  6  7  8
+ *  and the matrix should be: {230, 56, 114, 56, 114, 114, 56, 114, 56}.
+ *  However, since most of the valus are identical, we only use the first three
+ *  entries and the entries corresponding to the pixels is:
+ *  1  2  1
+ *  2  0  2
+ *  1  2  1
+ */
+const uint32_t convolution_matrix[3] = {230, 56, 114};
+
+/*
+ * Generate a blurred random noise bitmap.
+ */
+void GenerateBlurredNoise(void* dst_pixels, AndroidBitmapInfo* dst_info,
+      float noise_scale) {
+  uint32_t fixed_noise_scale = noise_scale * kShiftValue;
+
+  // Clear dst bitmap to 0 for storing generated random  noise.
+  memset(dst_pixels, 0, dst_info->stride * dst_info->height);
+
+  // 0.5 is a empirical value and could be tuned.
+  int random_threshold = RAND_MAX * 0.5;
+  for (uint32_t y = 0; y < dst_info->height; y++) {
+    uint32_t* dp_line = reinterpret_cast<uint32_t*>(
+        reinterpret_cast<char*>(dst_pixels) + y * dst_info->stride);
+
+    for (uint32_t x = 0; x < dst_info->width; x++) {
+      if (rand() < random_threshold) {
+        uint32_t* dp = dp_line + x;
+        uint32_t* dp_prev = (y == 0) ? dp : (dp - dst_info->width);
+        uint32_t* dp_next = (y == dst_info->height - 1) ? dp : (dp + dst_info->width);
+
+        /*
+         * 1  2  3
+         * 4  0  5
+         * 6  7  8
+         */
+        uint32_t* n[9];
+        n[0] = dp;
+        n[2] = dp_prev;
+        n[7] = dp_next;
+        if (x == 0) {
+          n[1] = n[2];
+          n[4] = n[0];
+          n[6] = n[7];
+        } else {
+          n[1] = n[2] - 1;
+          n[4] = n[0] - 1;
+          n[6] = n[7] - 1;
+        }
+        if (x == dst_info->width - 1) {
+          n[3] = n[2];
+          n[5] = n[0];
+          n[8] = n[7];
+        } else {
+          n[3] = n[2] + 1;
+          n[5] = n[0] + 1;
+          n[8] = n[7] + 1;
+        }
+
+        // noise randomness uniformly distributed between 0.5 to 1.5,
+        // 0.5 is an empirical value.
+        uint32_t random_noise_scale = fixed_noise_scale
+            * (static_cast<double>(rand()) / RAND_MAX + 0.5);
+
+        *n[0] = *n[0] + ((convolution_matrix[0] * random_noise_scale) >> kShiftBits);
+        // The value in convolution_matrix is identical (56) for indexes 1, 3, 6, 8.
+        uint32_t normal_scaled_noise = (convolution_matrix[1] * random_noise_scale) >> kShiftBits;
+        *n[1] += normal_scaled_noise;
+        *n[3] += normal_scaled_noise;
+        *n[6] += normal_scaled_noise;
+        *n[8] += normal_scaled_noise;
+        // Likewise, the computation could be saved for indexes 2, 4, 5, 7;
+        normal_scaled_noise = (convolution_matrix[2] * random_noise_scale) >> kShiftBits;
+        *n[2] += normal_scaled_noise;
+        *n[4] += normal_scaled_noise;
+        *n[5] += normal_scaled_noise;
+        *n[7] += normal_scaled_noise;
+      }
+    }
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeGrain(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat noise_scale) {
+   pGrainType f = (pGrainType)JNIFunc[JNI_Grain].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, noise_scale);
+}
+   
+extern "C" void Grain(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat noise_scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in grain failed, error=%d", ret);
+    return;
+  }
+
+  GenerateBlurredNoise(dst_pixels, &dst_info, noise_scale);
+
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    uint32_t* src = reinterpret_cast<uint32_t*>(
+        reinterpret_cast<char*>(src_pixels) + src_info.stride * scan_line);
+    uint32_t* dst = reinterpret_cast<uint32_t*>(
+        reinterpret_cast<char*>(dst_pixels) + dst_info.stride * scan_line);
+
+    uint32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      pixel32_t* sp = reinterpret_cast<pixel32_t*>(src);
+      pixel32_t* dp = reinterpret_cast<pixel32_t*>(dst);
+
+      // energy_mask is used to constrain the noise according to the energy
+      // level. Film grain appear more in dark part.
+      // The energy level (from 0 to 765) is square-rooted and should in the
+      // range from 0 to 27.659 (sqrt(765)), so 28 is used for normalization.
+      uint32_t energy_level = sp->rgba8[0] + sp->rgba8[1] + sp->rgba8[2];
+      uint32_t energy_mask = 28 - static_cast<uint32_t>(sqrtf(energy_level));
+
+      // The intensity of each channel of RGB is affected by the random
+      // noise previously produced and stored in dp->pixel.
+      // dp->pixel should be in the range of [1.3 * noise_scale * kShiftValue,
+      // 0]. Therefore 'scale' should be in the range of
+      // [kShiftValue, kShiftValue - 1.3 * noise_scale * kShiftValue]
+      uint32_t scale = (kShiftValue - dp->rgba32 * energy_mask / 28);
+      uint32_t red = (sp->rgba8[0] * scale) >> kShiftBits;
+      uint32_t green = (sp->rgba8[1] * scale) >> kShiftBits;
+      uint32_t blue = (sp->rgba8[2] * scale) >> kShiftBits;
+      dp->rgba32 = (sp->rgba8[3] << 24) | (blue << 16) | (green << 8) | red;
+
+      dst++;
+      src++;
+    }
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/grayscale.cpp b/samples/PhotoEditor/jni/grayscale.cpp
new file mode 100644
index 0000000..8b528f8
--- /dev/null
+++ b/samples/PhotoEditor/jni/grayscale.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeGrayscale(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pGrayscaleType f = (pGrayscaleType)JNIFunc[JNI_Grayscale].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+
+extern "C" void Grayscale(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in GrayScale failed, error=%d", ret);
+    return;
+  }
+
+  const int kShiftBits = 20;
+  const int32_t kRedRatio = static_cast<int32_t>((1 << kShiftBits) * 0.21f);
+  const int32_t kGreenRatio = static_cast<int32_t>((1 << kShiftBits) * 0.71f);
+  const int32_t kBlueRatio = static_cast<int32_t>((1 << kShiftBits) * 0.07f);
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      int32_t src_red = src->rgba8[0];
+      int32_t src_green = src->rgba8[1];
+      int32_t src_blue = src->rgba8[2];
+      int32_t src_alpha = src->rgba8[3];
+
+      int32_t dst_color = (kRedRatio * src_red + kGreenRatio * src_green +
+          kBlueRatio * src_blue) >> kShiftBits;
+      if (dst_color > 255) {
+        dst_color = 255;
+      }
+      *dst = (src_alpha << 24) | (dst_color << 16) | (dst_color << 8) | dst_color;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/heq.cpp b/samples/PhotoEditor/jni/heq.cpp
new file mode 100644
index 0000000..eb4d28d
--- /dev/null
+++ b/samples/PhotoEditor/jni/heq.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::clamp;
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+  // Array of approximated CDF of normal distribution.
+  // 1024 Entries in total. The array is approximated by sigmoid function and
+  // the exact command in "octave" is:
+  // x = [2/1029:1/1029:1025/1029];
+  // y = (-1/11.5*log(1./x-0.9999)+0.5)*766*0.9+766*0.05;
+  const int kCDFEntries = 1024;
+  const uint32_t normal_cdf[] = {
+    9, 33, 50, 64, 75, 84, 92, 99, 106, 112, 117, 122, 126, 130, 134, 138, 142,
+    145, 148, 150, 154, 157, 159, 162, 164, 166, 169, 170, 173, 175, 177, 179,
+    180, 182, 184, 186, 188, 189, 190, 192, 194, 195, 197, 198, 199, 200, 202,
+    203, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219,
+    220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 229, 230, 231, 232, 233,
+    234, 235, 236, 236, 237, 238, 239, 239, 240, 240, 242, 242, 243, 244, 245,
+    245, 246, 247, 247, 248, 249, 249, 250, 250, 251, 252, 253, 253, 254, 255,
+    255, 256, 256, 257, 258, 258, 259, 259, 259, 260, 261, 262, 262, 263, 263,
+    264, 264, 265, 265, 266, 267, 267, 268, 268, 269, 269, 269, 270, 270, 271,
+    272, 272, 273, 273, 274, 274, 275, 275, 276, 276, 277, 277, 277, 278, 278,
+    279, 279, 279, 280, 280, 281, 282, 282, 282, 283, 283, 284, 284, 285, 285,
+    285, 286, 286, 287, 287, 288, 288, 288, 289, 289, 289, 290, 290, 290, 291,
+    292, 292, 292, 293, 293, 294, 294, 294, 295, 295, 296, 296, 296, 297, 297,
+    297, 298, 298, 298, 299, 299, 299, 299, 300, 300, 301, 301, 302, 302, 302,
+    303, 303, 304, 304, 304, 305, 305, 305, 306, 306, 306, 307, 307, 307, 308,
+    308, 308, 309, 309, 309, 309, 310, 310, 310, 310, 311, 312, 312, 312, 313,
+    313, 313, 314, 314, 314, 315, 315, 315, 315, 316, 316, 316, 317, 317, 317,
+    318, 318, 318, 319, 319, 319, 319, 319, 320, 320, 320, 321, 321, 322, 322,
+    322, 323, 323, 323, 323, 324, 324, 324, 325, 325, 325, 325, 326, 326, 326,
+    327, 327, 327, 327, 328, 328, 328, 329, 329, 329, 329, 329, 330, 330, 330,
+    330, 331, 331, 332, 332, 332, 333, 333, 333, 333, 334, 334, 334, 334, 335,
+    335, 335, 336, 336, 336, 336, 337, 337, 337, 337, 338, 338, 338, 339, 339,
+    339, 339, 339, 339, 340, 340, 340, 340, 341, 341, 342, 342, 342, 342, 343,
+    343, 343, 344, 344, 344, 344, 345, 345, 345, 345, 346, 346, 346, 346, 347,
+    347, 347, 347, 348, 348, 348, 348, 349, 349, 349, 349, 349, 349, 350, 350,
+    350, 350, 351, 351, 352, 352, 352, 352, 353, 353, 353, 353, 354, 354, 354,
+    354, 355, 355, 355, 355, 356, 356, 356, 356, 357, 357, 357, 357, 358, 358,
+    358, 358, 359, 359, 359, 359, 359, 359, 359, 360, 360, 360, 360, 361, 361,
+    362, 362, 362, 362, 363, 363, 363, 363, 364, 364, 364, 364, 365, 365, 365,
+    365, 366, 366, 366, 366, 366, 367, 367, 367, 367, 368, 368, 368, 368, 369,
+    369, 369, 369, 369, 369, 370, 370, 370, 370, 370, 371, 371, 372, 372, 372,
+    372, 373, 373, 373, 373, 374, 374, 374, 374, 374, 375, 375, 375, 375, 376,
+    376, 376, 376, 377, 377, 377, 377, 378, 378, 378, 378, 378, 379, 379, 379,
+    379, 379, 379, 380, 380, 380, 380, 381, 381, 381, 382, 382, 382, 382, 383,
+    383, 383, 383, 384, 384, 384, 384, 385, 385, 385, 385, 385, 386, 386, 386,
+    386, 387, 387, 387, 387, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389,
+    389, 390, 390, 390, 390, 391, 391, 392, 392, 392, 392, 392, 393, 393, 393,
+    393, 394, 394, 394, 394, 395, 395, 395, 395, 396, 396, 396, 396, 396, 397,
+    397, 397, 397, 398, 398, 398, 398, 399, 399, 399, 399, 399, 399, 400, 400,
+    400, 400, 400, 401, 401, 402, 402, 402, 402, 403, 403, 403, 403, 404, 404,
+    404, 404, 405, 405, 405, 405, 406, 406, 406, 406, 406, 407, 407, 407, 407,
+    408, 408, 408, 408, 409, 409, 409, 409, 409, 409, 410, 410, 410, 410, 411,
+    411, 412, 412, 412, 412, 413, 413, 413, 413, 414, 414, 414, 414, 415, 415,
+    415, 415, 416, 416, 416, 416, 417, 417, 417, 417, 418, 418, 418, 418, 419,
+    419, 419, 419, 419, 419, 420, 420, 420, 420, 421, 421, 422, 422, 422, 422,
+    423, 423, 423, 423, 424, 424, 424, 425, 425, 425, 425, 426, 426, 426, 426,
+    427, 427, 427, 427, 428, 428, 428, 429, 429, 429, 429, 429, 429, 430, 430,
+    430, 430, 431, 431, 432, 432, 432, 433, 433, 433, 433, 434, 434, 434, 435,
+    435, 435, 435, 436, 436, 436, 436, 437, 437, 437, 438, 438, 438, 438, 439,
+    439, 439, 439, 439, 440, 440, 440, 441, 441, 442, 442, 442, 443, 443, 443,
+    443, 444, 444, 444, 445, 445, 445, 446, 446, 446, 446, 447, 447, 447, 448,
+    448, 448, 449, 449, 449, 449, 449, 450, 450, 450, 451, 451, 452, 452, 452,
+    453, 453, 453, 454, 454, 454, 455, 455, 455, 456, 456, 456, 457, 457, 457,
+    458, 458, 458, 459, 459, 459, 459, 460, 460, 460, 461, 461, 462, 462, 462,
+    463, 463, 463, 464, 464, 465, 465, 465, 466, 466, 466, 467, 467, 467, 468,
+    468, 469, 469, 469, 469, 470, 470, 470, 471, 472, 472, 472, 473, 473, 474,
+    474, 474, 475, 475, 476, 476, 476, 477, 477, 478, 478, 478, 479, 479, 479,
+    480, 480, 480, 481, 482, 482, 483, 483, 484, 484, 484, 485, 485, 486, 486,
+    487, 487, 488, 488, 488, 489, 489, 489, 490, 490, 491, 492, 492, 493, 493,
+    494, 494, 495, 495, 496, 496, 497, 497, 498, 498, 499, 499, 499, 500, 501,
+    502, 502, 503, 503, 504, 504, 505, 505, 506, 507, 507, 508, 508, 509, 509,
+    510, 510, 511, 512, 513, 513, 514, 515, 515, 516, 517, 517, 518, 519, 519,
+    519, 520, 521, 522, 523, 524, 524, 525, 526, 526, 527, 528, 529, 529, 530,
+    531, 532, 533, 534, 535, 535, 536, 537, 538, 539, 539, 540, 542, 543, 544,
+    545, 546, 547, 548, 549, 549, 550, 552, 553, 554, 555, 556, 558, 559, 559,
+    561, 562, 564, 565, 566, 568, 569, 570, 572, 574, 575, 577, 578, 579, 582,
+    583, 585, 587, 589, 590, 593, 595, 597, 599, 602, 604, 607, 609, 612, 615,
+    618, 620, 624, 628, 631, 635, 639, 644, 649, 654, 659, 666, 673, 680, 690,
+    700, 714};
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeHEQ(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pHEQType f = (pHEQType)JNIFunc[JNI_HEQ].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+   
+extern "C" void HEQ(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in HEQ failed, error=%d", ret);
+    return;
+  }
+
+  const int kEnergyLevels = 766;  // 255 * 3 + 1
+
+  // The energy has the range of [0, kEnergyLevels]
+  int accumulated_histogram[kEnergyLevels];
+  memset(accumulated_histogram, 0, sizeof(accumulated_histogram));
+
+  // Store all the energy in the dst_pixels
+  pixel32_t* dst = reinterpret_cast<pixel32_t*>(dst_pixels);
+  pixel32_t const* src = reinterpret_cast<pixel32_t const*>(src_pixels);
+  pixel32_t const* src_end = reinterpret_cast<pixel32_t const*>(
+      reinterpret_cast<char const*>(src) + src_info.stride * src_info.height);
+  while (src < src_end) {
+    dst->rgba32 = src->rgba8[0] + src->rgba8[1] + src->rgba8[2];
+    ++src;
+    ++dst;
+  }
+
+  // Build up the accumulated histogram table by ignoring borders (1/20 = 5% width).
+  float border_thickness_ratio = 0.05;
+  int y_border_thickness = dst_info.height * border_thickness_ratio;
+  pixel32_t* dst_line = reinterpret_cast<pixel32_t*>(
+      reinterpret_cast<char*>(dst_pixels) + dst_info.stride * y_border_thickness);
+  pixel32_t* dst_line_end = reinterpret_cast<pixel32_t*>(
+      reinterpret_cast<char*>(dst_pixels) + dst_info.stride *
+      (dst_info.height - y_border_thickness));
+  int x_border_thickness = dst_info.width * border_thickness_ratio;
+  int x_border_end = dst_info.width - x_border_thickness;
+  while (dst_line < dst_line_end) {
+    pixel32_t* dp = dst_line + x_border_thickness;
+    pixel32_t* dp_end = dst_line + x_border_end;
+    while (dp < dp_end) {
+      ++accumulated_histogram[dp->rgba32];
+      ++dp;
+    }
+    dst_line = reinterpret_cast<pixel32_t*>(
+        reinterpret_cast<char*>(dst_line) + dst_info.stride);
+  }
+
+  for (int i = 1; i < kEnergyLevels; i++) {
+    accumulated_histogram[i] += accumulated_histogram[i - 1];
+  }
+
+  uint32_t const* src_line =
+      reinterpret_cast<uint32_t const*>(src_pixels);
+  dst_line = reinterpret_cast<pixel32_t*>(dst_pixels);
+  dst_line_end = reinterpret_cast<pixel32_t*>(reinterpret_cast<char*>(dst_line) +
+      dst_info.height * dst_info.stride);
+  // The whole process is done by apply the HEQ result with a mask.
+  // The mask is a curve segmented by the energy_middle which could be tuned
+  // based on each bitmap. For the lower part, the mask tries to make the change
+  // significant for greater energy. For the smaller part, the mask does the
+  // contrary. The two curve should be continuous on the energy_middle so the
+  // final result is more natural. In this implementation, what I defined is two
+  // curve based on the energy 'e', for the smaller part, e^2 is used. For the
+  // greater part, e^1.5 is used. That is, for pixel with energy 'e', the mask
+  // is obtained by:
+  // if e > energy_middle
+  //     (e - energy_middle)^1.5 / (765 - energy_middle)^1.5
+  // else
+  //     (e - energy_middle)^2 / (energy_middle)^2
+  const int kShiftBits = 10;
+  const int kShiftValue = (1 << kShiftBits);
+  const int scale_shifted = scale * kShiftValue;
+  const int normalization_scale_shifted = (1.0 - scale) * kShiftValue;
+  const int energy_middle = 382;  // 765 / 2 = 382.5
+  const int normalization_low = 7481;  // (765 - 382.5)^1.5
+  const int normalization_high = 146307;  // 382.5^2
+  int total_pixels = accumulated_histogram[kEnergyLevels - 1];
+  while (dst_line < dst_line_end) {
+    pixel32_t const* sp = reinterpret_cast<pixel32_t const*>(src_line);
+    pixel32_t* dp = dst_line;
+    pixel32_t* dp_end = dp + dst_info.width;
+    while (dp < dp_end) {
+      if (!dp->rgba32) {  // the energy is 0, no changes will be made.
+        dp->rgba32 = sp->rgba32;
+      } else {
+        uint32_t energy = dp->rgba32;
+        int mask_normalization;
+        int mask_value = energy - energy_middle;
+
+        if (mask_value > 0) {
+          mask_value = mask_value * sqrt(mask_value);
+          mask_normalization = normalization_low;
+        } else {
+          mask_value *= mask_value;
+          mask_normalization = normalization_high;
+        }
+        mask_value = ((mask_value * scale_shifted) +
+            (mask_normalization * (normalization_scale_shifted))) >> kShiftBits;
+        // The final picture is masked by the original energy.
+        // Assumption: Lower energy can result in low-confidence information and
+        // higher energy indicates good confidence.
+        // Therefore, pixels with low and high confidence should not be changed
+        // greatly.
+        uint32_t dst_energy = normal_cdf[
+            kCDFEntries * accumulated_histogram[energy] / total_pixels];
+        dst_energy = (energy * mask_value + dst_energy * (mask_normalization - mask_value)) /
+            mask_normalization;
+
+        // Ensure there is no RGB value will be greater than 255.
+        uint32_t max_energy = energy * 255 / MAX3(sp->rgba8[0], sp->rgba8[1], sp->rgba8[2]);
+        if (dst_energy > max_energy) {
+          dst_energy = max_energy;
+        }
+
+        dst_energy = (dst_energy << kShiftBits) / energy;
+        uint32_t dst_red = (sp->rgba8[0] * dst_energy) >> kShiftBits;
+        uint32_t dst_green = (sp->rgba8[1] * dst_energy) >> kShiftBits;
+        uint32_t dst_blue = (sp->rgba8[2] * dst_energy) >> kShiftBits;
+        dp->rgba32 = (sp->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      }
+      dp++;
+      sp++;
+    }
+    src_line = reinterpret_cast<uint32_t const*>(
+        reinterpret_cast<char const*>(src_line) + src_info.stride);
+    dst_line = reinterpret_cast<pixel32_t*>(
+        reinterpret_cast<char*>(dst_line) + dst_info.stride);
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/negative.cpp b/samples/PhotoEditor/jni/negative.cpp
new file mode 100644
index 0000000..ee90609
--- /dev/null
+++ b/samples/PhotoEditor/jni/negative.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeNegative(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+    pNegativeType f = (pNegativeType)JNIFunc[JNI_Negative].func_ptr;
+    return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void Negative(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Negative failed, error=%d", ret);
+    return;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      uint32_t dst_red = 0xff - src->rgba8[0];
+      uint32_t dst_green = 0xff - src->rgba8[1];
+      uint32_t dst_blue = 0xff - src->rgba8[2];
+      uint32_t src_alpha = src->rgba8[3];
+
+      *dst = (src_alpha << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/quantize.cpp b/samples/PhotoEditor/jni/quantize.cpp
new file mode 100644
index 0000000..cd43f66
--- /dev/null
+++ b/samples/PhotoEditor/jni/quantize.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeQuantize(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pQuantizeType f = (pQuantizeType)JNIFunc[JNI_Quantize].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void Quantize(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in quantize failed, error=%d", ret);
+    return;
+  }
+
+  uint8_t quantize_map[256];
+
+  for (uint32_t i = 0; i < 256; i++) {
+    quantize_map[i] = i / 128 * 128 + 64;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* dst = reinterpret_cast<pixel32_t*>(dst_pixels);
+
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      dst->rgba8[0] = quantize_map[src->rgba8[0]];
+      dst->rgba8[1] = quantize_map[src->rgba8[1]];
+      dst->rgba8[2] = quantize_map[src->rgba8[2]];
+      dst->rgba8[3] = src->rgba8[3];
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/redeye.cpp b/samples/PhotoEditor/jni/redeye.cpp
new file mode 100644
index 0000000..af828d8
--- /dev/null
+++ b/samples/PhotoEditor/jni/redeye.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeRedEye(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap,
+    jobjectArray redeye_positions, jfloat radius, jfloat intensity) {
+   pRedEyeType f = (pRedEyeType)JNIFunc[JNI_RedEye].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, redeye_positions, radius, intensity);
+}
+   
+extern "C" void RedEye(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap,
+    jobjectArray redeye_positions, jfloat radius, jfloat intensity) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in RedEye failed, error=%d", ret);
+    return;
+  }
+
+  memcpy(dst_pixels, src_pixels, src_info.stride * src_info.height);
+
+  // get the class reference.
+  jclass cls = env->FindClass("android/graphics/PointF");
+  jfieldID xid = env->GetFieldID(cls, "x", "F");
+  jfieldID yid = env->GetFieldID(cls, "y", "F");
+  int eye_number = env->GetArrayLength(redeye_positions);
+  for (int eye = 0; eye < eye_number; eye++) {
+    jobject point_f = env->GetObjectArrayElement(redeye_positions, eye);
+    float focus_x = env->GetFloatField(point_f, xid);
+    float focus_y = env->GetFloatField(point_f, yid);
+    if (focus_x < 0.0 || focus_x > 1.0 || focus_y < 0.0 || focus_y > 1.0) {
+      // did not tap on the picture
+      continue;
+    }
+
+    float x = focus_x * src_info.width;
+    float y = focus_y * src_info.height;
+    uint32_t bound_y = y + radius;
+    if (bound_y > dst_info.height) {
+      bound_y = dst_info.height;
+    }
+    uint32_t start_x = (x > radius) ? x - radius: 0;
+    uint32_t bound_x = x + radius;
+    if (bound_x > dst_info.width) {
+      bound_x = dst_info.width;
+    }
+    uint32_t dst_y = (y > radius) ? y - radius : 0;
+    for (; dst_y < bound_y; dst_y++) {
+      uint32_t *dst = reinterpret_cast<uint32_t*>(
+          reinterpret_cast<char*>(dst_pixels) + dst_info.stride * dst_y) + start_x;
+      pixel32_t *src = reinterpret_cast<pixel32_t*>(
+          reinterpret_cast<char*>(src_pixels) + src_info.stride * dst_y) + start_x;
+      for (uint32_t dst_x = start_x; dst_x < bound_x; dst_x++) {
+        if (hypotf(dst_x - x, dst_y - y) <= radius) {
+          int32_t red = src->rgba8[0];
+          int32_t green = src->rgba8[1];
+          int32_t blue = src->rgba8[2];
+          int32_t alpha = src->rgba8[3];
+
+          float redIntensity = static_cast<float>(red) / (green + blue);
+          if (redIntensity > intensity) {
+            red = (green + blue) / 2.0f;
+            *dst = alpha << 24 | (blue << 16) | (green << 8) | red;
+          }
+        }
+        dst++;
+        src++;
+      }  // dst_x
+    }  // dst_y
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/saturate.cpp b/samples/PhotoEditor/jni/saturate.cpp
new file mode 100644
index 0000000..29693e7
--- /dev/null
+++ b/samples/PhotoEditor/jni/saturate.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+const int k256Multiply255 = 65280;
+
+void BenSaturate(AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+    void *src_pixels, void *dst_pixels, int scale) {
+  for (uint32_t scan_line = 0; scan_line < dst_info->height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_end = src + src_info->width;
+
+    while (src < src_end) {
+      int kv = (src->rgba8[0] + src->rgba8[0] + src->rgba8[1] * 5 + src->rgba8[2] + 4) >> 3;
+
+      int dst_red = kv + (((src->rgba8[0] - kv) * scale) >> 8);
+      int dst_green = kv + (((src->rgba8[1] - kv) * scale) >> 8);
+      int dst_blue = kv + (((src->rgba8[2] - kv) * scale) >> 8);
+
+      *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info->stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info->stride;
+  }
+}
+
+void HerfSaturate(AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+      void *src_pixels, void *dst_pixels, float scale) {
+  const float kMapScale = 8.0;
+  const int kMapSize = 2048;
+  int rmap[kMapSize];
+  int gmap[kMapSize];
+  int bmap[kMapSize];
+
+  const float kR = 0.3;
+  const float kG = 0.7;
+  const float kB = 0.9;
+  const float amtR = (kR * scale) + 1;
+  const float amtG = (kG * scale) + 1;
+  const float amtB = (kB * scale) + 1;
+
+  for (int i = 0; i < kMapSize; i++) {
+      float inv = kMapScale * static_cast<float>(i) / kMapSize;
+      rmap[i] = static_cast<int>(pow(inv, amtR) * 256);
+      gmap[i] = static_cast<int>(pow(inv, amtG) * 256);
+      bmap[i] = static_cast<int>(pow(inv, amtB) * 256);
+  }
+
+  int kMapFactor256 = static_cast<int>(kMapSize / kMapScale) * 256;
+  for (uint32_t scan_line = 0; scan_line < dst_info->height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_end = src + src_info->width;
+
+    while (src < src_end) {
+      int de = (src->rgba8[0] + src->rgba8[0] + src->rgba8[1] * 5 + src->rgba8[2]) >> 3;
+      if (!de) {
+          *dst = src->rgba8[3] << 24;
+      } else {
+        int invde = (kMapFactor256 - 1) / de;
+        int r = invde * src->rgba8[0] >> 8;
+        int g = invde * src->rgba8[1] >> 8;
+        int b = invde * src->rgba8[2] >> 8;
+
+        int dst_red = de * rmap[r] >> 8;
+        int dst_green = de * gmap[g] >> 8;
+        int dst_blue = de * bmap[b] >> 8;
+
+        int rgb_max = MAX3(dst_red, dst_green, dst_blue);
+        if (rgb_max > 255) {
+          int invmax = k256Multiply255 / rgb_max;
+          dst_red = dst_red * invmax >> 8;
+          dst_green = dst_green * invmax >> 8;
+          dst_blue = dst_blue * invmax >> 8;
+        }
+
+        *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      }
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info->stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info->stride;
+  }
+}
+
+/*
+ * @param scale ranges from -1 to 1.
+ */
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeSaturation(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pSaturationType f = (pSaturationType)JNIFunc[JNI_Saturation].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+   
+extern "C" void Saturation(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Saturation failed, error=%d", ret);
+    return;
+  }
+
+  if (scale >= 0) {
+    HerfSaturate(&src_info, &dst_info, src_pixels, dst_pixels, scale * 3);
+  } else {
+    BenSaturate(&src_info, &dst_info, src_pixels, dst_pixels,
+        static_cast<int>((1 + scale) * 256));
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/sepia.cpp b/samples/PhotoEditor/jni/sepia.cpp
new file mode 100644
index 0000000..ea54d4c
--- /dev/null
+++ b/samples/PhotoEditor/jni/sepia.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeSepia(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pSepiaType f = (pSepiaType)JNIFunc[JNI_Sepia].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+
+extern "C" void Sepia(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Sepia failed, error=%d", ret);
+    return;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      int dst_red = (src->rgba8[0] * 805 + src->rgba8[1] * 1575 + src->rgba8[2] * 387) >> 11;
+      int dst_green = (src->rgba8[0] * 715 + src->rgba8[1] * 1405 + src->rgba8[2] * 344) >> 11;
+      int dst_blue = (src->rgba8[0] * 557 + src->rgba8[1] * 1094 + src->rgba8[2] * 268) >> 11;
+
+      if (dst_red > 255) {
+        dst_red = 255;
+      }
+      if (dst_green > 255) {
+        dst_green = 255;
+      }
+      if (dst_blue > 255) {
+        dst_blue = 255;
+      }
+
+      *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/sharpen.cpp b/samples/PhotoEditor/jni/sharpen.cpp
new file mode 100644
index 0000000..3c0514e
--- /dev/null
+++ b/samples/PhotoEditor/jni/sharpen.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "convolution.h"
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::convolution::SpecialConvolution;
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeSharpen(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+   pSharpenType f = (pSharpenType)JNIFunc[JNI_Sharpen].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, scale);
+}
+
+extern "C" void Sharpen(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat scale) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Sharpen failed, error=%d", ret);
+    return;
+  }
+
+  SpecialConvolution(&src_info, &dst_info, src_pixels, dst_pixels, -scale);
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/tint.cpp b/samples/PhotoEditor/jni/tint.cpp
new file mode 100644
index 0000000..23c7fe4
--- /dev/null
+++ b/samples/PhotoEditor/jni/tint.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeTint(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint tint) {
+   pTintType f = (pTintType)JNIFunc[JNI_Tint].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, tint);
+}
+
+extern "C" void Tint(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jint tint) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Tint failed, error=%d", ret);
+    return;
+  }
+
+  const int kShiftBits = 10;
+  const int kRedRatio = static_cast<int>(0.8f * (1 << kShiftBits) * 0.21f);
+  const int kGreenRatio = static_cast<int>(0.8f * (1 << kShiftBits) * 0.71f);
+  const int kBlueRatio = static_cast<int>(0.8f * (1 << kShiftBits) * 0.07f);
+  const int kTintRatio = static_cast<int>(0.2f * (1 << kShiftBits));
+
+  uint32_t tint_red = kTintRatio * ((tint >> 16) & 0xff);
+  uint32_t tint_green = kTintRatio * ((tint >> 8) & 0xff);
+  uint32_t tint_blue = kTintRatio * (tint & 0xff);
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      int32_t avg = kRedRatio * src->rgba8[0] + kGreenRatio * src->rgba8[1] +
+          kBlueRatio * src->rgba8[2];
+
+      int32_t dst_red = (avg + tint_red) >> kShiftBits;
+      int32_t dst_green = (avg + tint_green) >> kShiftBits;
+      int32_t dst_blue = (avg + tint_blue) >> kShiftBits;
+
+      if (dst_red > 255) {
+        dst_red = 255;
+      }
+      if (dst_green > 255) {
+        dst_green = 255;
+      }
+      if (dst_blue > 255) {
+        dst_blue = 255;
+      }
+
+      *dst = (src->rgba8[3] << 24) | (dst_blue << 16) | (dst_green << 8) | dst_red;
+      dst++;
+      src++;
+    }
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/utils.cpp b/samples/PhotoEditor/jni/utils.cpp
new file mode 100644
index 0000000..d16eceb
--- /dev/null
+++ b/samples/PhotoEditor/jni/utils.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+#include <android/bitmap.h>
+
+namespace android {
+namespace apps {
+namespace photoeditor {
+namespace utils {
+
+int ExtractInfoFromBitmap(JNIEnv *env,
+    AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+    jobject src_bitmap, jobject dst_bitmap) {
+  int ret;
+  /*
+   * The info->format of both bitmap should be ANDROID_BITMAP_FORMAT_RGBA_8888.
+   * Both bitmaps shall also be in the same dimension.
+   */
+  if ((ret = AndroidBitmap_getInfo(env, src_bitmap, src_info)) < 0) {
+    LOGE("AndroidBitmap_getInfo(src_bitmap) failed, error=%d", ret);
+    return ret;
+  }
+  if ((ret = AndroidBitmap_getInfo(env, dst_bitmap, dst_info)) < 0) {
+    LOGE("AndroidBitmap_getInfo(dst_bitmap) failed, error=%d", ret);
+    return ret;
+  }
+  return 0;
+}
+
+int LockBitmaps(JNIEnv* env, jobject src_bitmap, jobject dst_bitmap,
+    AndroidBitmapInfo* src_info, AndroidBitmapInfo* dst_info,
+    void** src_pixels, void** dst_pixels) {
+  int ret;
+  if ((ret = ExtractInfoFromBitmap(env, src_info, dst_info, src_bitmap, dst_bitmap)) < 0) {
+    return ret;
+  }
+  if ((ret = AndroidBitmap_lockPixels(env, src_bitmap, src_pixels)) < 0) {
+    LOGE("AndroidBitmap_lockPixels(src_bitmap) failed, error=%d", ret);
+    return ret;
+  }
+  if ((ret = AndroidBitmap_lockPixels(env, dst_bitmap, dst_pixels)) < 0) {
+    LOGE("AndroidBitmap_lockPixels(dst_bitmap) failed, error=%d", ret);
+    AndroidBitmap_unlockPixels(env, src_bitmap);
+    return ret;
+  }
+  return 0;
+}
+
+void UnlockBitmaps(JNIEnv* env, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmap_unlockPixels(env, src_bitmap);
+  AndroidBitmap_unlockPixels(env, dst_bitmap);
+}
+
+}  // namespace utils
+}  // namespace photoeditor
+}  // namespace apps
+}  // namespace android
+
diff --git a/samples/PhotoEditor/jni/utils.h b/samples/PhotoEditor/jni/utils.h
new file mode 100644
index 0000000..af4bb54
--- /dev/null
+++ b/samples/PhotoEditor/jni/utils.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PHOTOEDITOR_JNI_UTILS_H_
+#define PHOTOEDITOR_JNI_UTILS_H_
+
+#include <android/bitmap.h>
+#include <android/log.h>
+#include <jni.h>
+
+#define  LOG_TAG    "libjni_photoeditor"
+#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+#define MIN3(x, y, z)  ((y) <= (z) ? \
+                         ((x) <= (y) ? (x) : (y)) \
+                     : \
+                         ((x) <= (z) ? (x) : (z)))
+
+#define MAX3(x, y, z)  ((y) >= (z) ? \
+                         ((x) >= (y) ? (x) : (y)) \
+                     : \
+                         ((x) >= (z) ? (x) : (z)))
+
+namespace android {
+namespace apps {
+namespace photoeditor {
+namespace utils {
+
+inline int clamp(int x, int min, int max) {
+  return (x < min ? min : ((x > max) ? max : x));
+}
+
+union pixel32_t {
+  uint32_t rgba32;
+  uint8_t rgba8[4];  // 0: red 1:green 2:blue 3:alpha
+};
+
+typedef union pixel32_t pixel32_t;
+
+int ExtractInfoFromBitmap(JNIEnv *env,
+    AndroidBitmapInfo *src_info, AndroidBitmapInfo *dst_info,
+    jobject src_bitmap, jobject dst_bitmap);
+
+int LockBitmaps(JNIEnv* env, jobject src_bitmap, jobject dst_bitmap,
+    AndroidBitmapInfo* src_info, AndroidBitmapInfo* dst_info,
+    void** src_pixels, void** dst_pixels);
+
+void UnlockBitmaps(JNIEnv* env, jobject src_bitmap, jobject dst_bitmap);
+
+}  // namespace utils
+}  // namespace photoeditor
+}  // namespace apps
+}  // namespace android
+
+
+#endif  // PHOTOEDITOR_JNI_UTILS_H_
diff --git a/samples/PhotoEditor/jni/vignetting.cpp b/samples/PhotoEditor/jni/vignetting.cpp
new file mode 100644
index 0000000..3451e9d
--- /dev/null
+++ b/samples/PhotoEditor/jni/vignetting.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+/**
+ * @param range the position of the significant falloff of light (50% luminosity).
+ */
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeVignetting(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat range) {
+   pVignettingType f = (pVignettingType)JNIFunc[JNI_Vignetting].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, range);
+}
+
+extern "C" void Vignetting(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat range) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  const float slope = 20.0f;   // how fast the light changes, the bigger the faster.
+  range *= slope;
+  const int kShiftBits = 10;
+  const uint32_t shade = 0.85 * (1 << kShiftBits);   // the intensity of the shade
+  const uint32_t shade_offset = (1 << kShiftBits) - shade;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in Vignetting failed, error=%d", ret);
+    return;
+  }
+
+  const uint32_t center_x = src_info.width / 2;
+  const uint32_t center_y = src_info.height / 2;
+  const uint32_t hypot = sqrt(center_x * center_x + center_y * center_y) / slope;
+
+  for (uint32_t dst_y = 0; dst_y < dst_info.height; dst_y++) {
+    uint32_t *dst = reinterpret_cast<uint32_t*>(
+        reinterpret_cast<char*>(dst_pixels) + dst_info.stride * dst_y);
+    pixel32_t *src = reinterpret_cast<pixel32_t*>(
+        reinterpret_cast<char*>(src_pixels) + src_info.stride * dst_y);
+    int dy = dst_y - center_y;
+    for (uint32_t dst_x = 0; dst_x < dst_info.width; dst_x++) {
+      int dx = dst_x - center_x;
+      int dist_square = dx * dx + dy * dy;
+      uint32_t luminousity = shade / (1 + exp(sqrt(dist_square) / hypot - range)) +
+          shade_offset;
+      uint32_t red = (luminousity * src->rgba8[0]) >> kShiftBits;
+      uint32_t green = (luminousity * src->rgba8[1]) >> kShiftBits;
+      uint32_t blue = (luminousity * src->rgba8[2]) >> kShiftBits;
+      uint32_t alpha = src->rgba8[3];
+      *dst = (alpha << 24) | (blue << 16) | (green << 8) | red;
+      dst++;
+      src++;
+    }  // dst_x
+  }  // dst_y
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/warmify.cpp b/samples/PhotoEditor/jni/warmify.cpp
new file mode 100644
index 0000000..5bf702a
--- /dev/null
+++ b/samples/PhotoEditor/jni/warmify.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+const int map[256] = {
+    0xff000000,0xff010101,0xff030201,0xff040302,0xff050303,0xff060403,0xff080504,0xff090605,
+    0xff0a0705,0xff0c0806,0xff0d0907,0xff0e0907,0xff0f0a08,0xff110b09,0xff120c09,0xff130d0a,
+    0xff140e0b,0xff160f0b,0xff17100c,0xff18100d,0xff1a110d,0xff1b120e,0xff1c130f,0xff1d140f,
+    0xff1f1510,0xff201611,0xff211711,0xff221812,0xff241913,0xff251a13,0xff261b14,0xff271c15,
+    0xff291d15,0xff2a1e16,0xff2b1f17,0xff2c2017,0xff2e2118,0xff2f2119,0xff30221a,0xff31231a,
+    0xff32241b,0xff34251c,0xff35261c,0xff36271d,0xff37281e,0xff38291e,0xff3a2a1f,0xff3b2b20,
+    0xff3c2c20,0xff3d2d21,0xff3e2e22,0xff402f23,0xff413023,0xff423124,0xff433225,0xff443325,
+    0xff463426,0xff473527,0xff483628,0xff493728,0xff4a3829,0xff4b392a,0xff4c3a2b,0xff4e3b2b,
+    0xff4f3c2c,0xff503d2d,0xff513e2d,0xff523f2e,0xff53402f,0xff544130,0xff554130,0xff564231,
+    0xff574332,0xff594433,0xff5a4533,0xff5b4634,0xff5c4735,0xff5d4836,0xff5e4937,0xff5f4a37,
+    0xff604b38,0xff614c39,0xff624c3a,0xff634d3a,0xff644e3b,0xff654f3c,0xff66503d,0xff67513e,
+    0xff68523e,0xff69533f,0xff6a5340,0xff6b5441,0xff6c5542,0xff6d5642,0xff6e5743,0xff6f5844,
+    0xff705845,0xff715946,0xff715a47,0xff725b47,0xff735c48,0xff745c49,0xff755d4a,0xff765e4b,
+    0xff775f4c,0xff785f4c,0xff79604d,0xff7a614e,0xff7a624f,0xff7b6350,0xff7c6351,0xff7d6452,
+    0xff7e6552,0xff7f6653,0xff806654,0xff806755,0xff816856,0xff826957,0xff836958,0xff846a59,
+    0xff856b5a,0xff856c5a,0xff866c5b,0xff876d5c,0xff886e5d,0xff896f5e,0xff896f5f,0xff8a7060,
+    0xff8b7161,0xff8c7262,0xff8d7263,0xff8d7364,0xff8e7465,0xff8f7565,0xff907566,0xff907667,
+    0xff917768,0xff927869,0xff93796a,0xff94796b,0xff947a6c,0xff957b6d,0xff967c6e,0xff977c6f,
+    0xff977d70,0xff987e71,0xff997f72,0xff9a8073,0xff9a8074,0xff9b8175,0xff9c8276,0xff9d8377,
+    0xff9d8478,0xff9e8479,0xff9f857a,0xffa0867b,0xffa0877c,0xffa1887d,0xffa2897e,0xffa3897f,
+    0xffa38a80,0xffa48b81,0xffa58c82,0xffa68d83,0xffa68e84,0xffa78f85,0xffa89086,0xffa89087,
+    0xffa99188,0xffaa9289,0xffab938a,0xffab948b,0xffac958c,0xffad968e,0xffae978f,0xffae9890,
+    0xffaf9991,0xffb09992,0xffb19a93,0xffb19b94,0xffb29c95,0xffb39d96,0xffb49e97,0xffb49f98,
+    0xffb5a099,0xffb6a19a,0xffb7a29c,0xffb8a39d,0xffb8a49e,0xffb9a59f,0xffbaa6a0,0xffbba7a1,
+    0xffbba8a2,0xffbca9a3,0xffbdaaa4,0xffbeaba5,0xffbfaba7,0xffbfaca8,0xffc0ada9,0xffc1aeaa,
+    0xffc2afab,0xffc3b0ac,0xffc3b1ad,0xffc4b2ae,0xffc5b3af,0xffc6b4b1,0xffc7b5b2,0xffc7b6b3,
+    0xffc8b7b4,0xffc9b8b5,0xffcab9b6,0xffcbbab7,0xffccbbb8,0xffcdbcba,0xffcdbdbb,0xffcebebc,
+    0xffcfbfbd,0xffd0c0be,0xffd1c1bf,0xffd2c3c0,0xffd3c4c2,0xffd3c5c3,0xffd4c6c4,0xffd5c7c5,
+    0xffd6c8c6,0xffd7c9c7,0xffd8cac8,0xffd9cbca,0xffdacccb,0xffdacdcc,0xffdbcecd,0xffdccfce,
+    0xffddd0cf,0xffded1d1,0xffdfd2d2,0xffe0d3d3,0xffe1d4d4,0xffe2d5d5,0xffe3d6d6,0xffe3d7d7,
+    0xffe4d8d9,0xffe5d9da,0xffe6dadb,0xffe7dbdc,0xffe8dcdd,0xffe9ddde,0xffeadfe0,0xffebe0e1,
+    0xffece1e2,0xffede2e3,0xffede3e4,0xffeee4e5,0xffefe5e7,0xfff0e6e8,0xfff1e7e9,0xfff2e8ea
+};
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeWarmify(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+   pWarmifyType f = (pWarmifyType)JNIFunc[JNI_Warmify].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap);
+}
+   
+extern "C" void Warmify(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in nativeWarmify failed, error=%d", ret);
+    return;
+  }
+
+  for (uint32_t scan_line = 0; scan_line < dst_info.height; scan_line++) {
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+
+    while (src < src_line_end) {
+      int dst_red = (map[src->rgba8[0]] & 0xFF0000) >> 16;
+      int dst_green = map[src->rgba8[1]] & 0xFF00;
+      int dst_blue = (map[src->rgba8[2]] & 0xFF) << 16;
+
+      *dst = (src->rgba8[3] << 24) | dst_blue | dst_green | dst_red;
+
+      src++;
+      dst++;
+    }
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/jni/whiteblack.cpp b/samples/PhotoEditor/jni/whiteblack.cpp
new file mode 100644
index 0000000..6bc6c36
--- /dev/null
+++ b/samples/PhotoEditor/jni/whiteblack.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+#include <cmath>
+#include <cstdlib>
+#include <ctime>
+
+#include "utils.h"
+#include "_jni.h"
+
+using android::apps::photoeditor::utils::clamp;
+using android::apps::photoeditor::utils::LockBitmaps;
+using android::apps::photoeditor::utils::pixel32_t;
+using android::apps::photoeditor::utils::UnlockBitmaps;
+
+namespace {
+
+const int k256Multiply255 = 65280;
+
+void WhiteblackXF(float white, float black, int xform[]) {
+  float scale = (black != white) ? 1.0f / (white - black) : 2000.0f;
+  float offset = k256Multiply255 * black;
+  for (int i = 0; i < 256; i++) {
+    float vgamma = (256.0f * i - offset) * scale;
+    xform[i] = clamp(floor(vgamma), 0, k256Multiply255);
+  }
+  xform[256] = xform[255];
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_photoeditor_filters_ImageUtils_nativeWhiteBlack(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat white, jfloat black) {
+   pWhiteBlackType f = (pWhiteBlackType)JNIFunc[JNI_WhiteBlack].func_ptr;
+   return f(env, obj, src_bitmap, dst_bitmap, white, black);
+}
+
+extern "C" void WhiteBlack(
+    JNIEnv *env, jobject obj, jobject src_bitmap, jobject dst_bitmap, jfloat white, jfloat black) {
+  AndroidBitmapInfo src_info;
+  AndroidBitmapInfo dst_info;
+  void* src_pixels;
+  void* dst_pixels;
+
+  int ret = LockBitmaps(
+      env, src_bitmap, dst_bitmap, &src_info, &dst_info, &src_pixels, &dst_pixels);
+  if (ret < 0) {
+    LOGE("LockBitmaps in WhiteBlack failed, error=%d", ret);
+    return;
+  }
+
+  srandom(time(NULL));
+
+  int xform[257];
+  WhiteblackXF(white, black, xform);
+
+  for (uint32_t scan_line = 0; scan_line < src_info.height; scan_line++) {
+    pixel32_t* src = reinterpret_cast<pixel32_t*>(src_pixels);
+    uint32_t* dst = reinterpret_cast<uint32_t*>(dst_pixels);
+    pixel32_t* src_line_end = src + src_info.width;
+    while (src < src_line_end) {
+      int32_t red = src->rgba8[0];
+      int32_t green = src->rgba8[1];
+      int32_t blue = src->rgba8[2];
+      int32_t alpha = src->rgba8[3];
+
+      int32_t xform_red = xform[red];
+      int32_t xform_green = xform[green];
+      int32_t xform_blue = xform[blue];
+
+      red = xform[red + 1] - xform_red;
+      green = xform[green + 1] - xform_green;
+      blue = xform[blue + 1] - xform_blue;
+
+      int32_t dither = random() % 256;
+      int32_t diff_red = red * dither >> 8;
+      int32_t diff_green = green * dither >> 8;
+      int32_t diff_blue = blue * dither >> 8;
+
+      red = (xform_red + diff_red - (red >> 1)) >> 8;
+      green = (xform_green + diff_green - (green >> 1)) >> 8;
+      blue = (xform_blue + diff_blue - (blue >> 1)) >> 8;
+
+      red = clamp(red, 0, 255);
+      green = clamp(green, 0, 255);
+      blue = clamp(blue, 0, 255);
+
+      *dst = (alpha << 24) | (blue << 16) | (green << 8) | red;
+      src++;
+      dst++;
+    }
+    src_pixels = reinterpret_cast<char*>(src_pixels) + src_info.stride;
+    dst_pixels = reinterpret_cast<char*>(dst_pixels) + dst_info.stride;
+  }
+
+  UnlockBitmaps(env, src_bitmap, dst_bitmap);
+}
+
+}  // namespace
diff --git a/samples/PhotoEditor/res/anim/fade_in.xml b/samples/PhotoEditor/res/anim/fade_in.xml
new file mode 100644
index 0000000..7334041
--- /dev/null
+++ b/samples/PhotoEditor/res/anim/fade_in.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.25"
+    android:toAlpha="1.0"
+    android:duration="500"
+    android:fillAfter="true"/>
diff --git a/samples/PhotoEditor/res/anim/fade_out.xml b/samples/PhotoEditor/res/anim/fade_out.xml
new file mode 100644
index 0000000..ad97c49
--- /dev/null
+++ b/samples/PhotoEditor/res/anim/fade_out.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.25"
+    android:duration="500"
+    android:fillAfter="true"/>
diff --git a/samples/PhotoEditor/res/anim/wheel_hide.xml b/samples/PhotoEditor/res/anim/wheel_hide.xml
new file mode 100644
index 0000000..e79e346
--- /dev/null
+++ b/samples/PhotoEditor/res/anim/wheel_hide.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromXDelta="0%"
+    android:toXDelta="-100%"
+    android:duration="300"/>
diff --git a/samples/PhotoEditor/res/anim/wheel_show.xml b/samples/PhotoEditor/res/anim/wheel_show.xml
new file mode 100644
index 0000000..9fef1e2
--- /dev/null
+++ b/samples/PhotoEditor/res/anim/wheel_show.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromXDelta="-100%"
+    android:toXDelta="0%"
+    android:duration="300"/>
diff --git a/samples/PhotoEditor/res/drawable/actionbar_translucent.9.png b/samples/PhotoEditor/res/drawable/actionbar_translucent.9.png
new file mode 100644
index 0000000..f18761f
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/actionbar_translucent.9.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/arrow_back.png b/samples/PhotoEditor/res/drawable/arrow_back.png
new file mode 100644
index 0000000..897a1c1
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/arrow_back.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/arrow_down.png b/samples/PhotoEditor/res/drawable/arrow_down.png
new file mode 100644
index 0000000..ef6dd97
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/arrow_down.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/arrow_right.png b/samples/PhotoEditor/res/drawable/arrow_right.png
new file mode 100644
index 0000000..87893f7
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/arrow_right.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/autofix_off.png b/samples/PhotoEditor/res/drawable/autofix_off.png
new file mode 100644
index 0000000..f0e2d46
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/autofix_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/autofix_on.png b/samples/PhotoEditor/res/drawable/autofix_on.png
new file mode 100644
index 0000000..9f1da69
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/autofix_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/colortemperature_off.png b/samples/PhotoEditor/res/drawable/colortemperature_off.png
new file mode 100644
index 0000000..d4f1355
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/colortemperature_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/colortemperature_on.png b/samples/PhotoEditor/res/drawable/colortemperature_on.png
new file mode 100644
index 0000000..7569b57
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/colortemperature_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crop_height_holo.png b/samples/PhotoEditor/res/drawable/crop_height_holo.png
new file mode 100644
index 0000000..19fdb87
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crop_height_holo.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crop_off.png b/samples/PhotoEditor/res/drawable/crop_off.png
new file mode 100644
index 0000000..cdea423
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crop_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crop_on.png b/samples/PhotoEditor/res/drawable/crop_on.png
new file mode 100644
index 0000000..4f5c298
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crop_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crop_width_holo.png b/samples/PhotoEditor/res/drawable/crop_width_holo.png
new file mode 100644
index 0000000..3c82e11
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crop_width_holo.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crossprocess_off.png b/samples/PhotoEditor/res/drawable/crossprocess_off.png
new file mode 100644
index 0000000..f9e596a
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crossprocess_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/crossprocess_on.png b/samples/PhotoEditor/res/drawable/crossprocess_on.png
new file mode 100644
index 0000000..5564db3
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/crossprocess_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/documentary_off.png b/samples/PhotoEditor/res/drawable/documentary_off.png
new file mode 100644
index 0000000..92c40f9
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/documentary_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/documentary_on.png b/samples/PhotoEditor/res/drawable/documentary_on.png
new file mode 100644
index 0000000..ff63406
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/documentary_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/doodle_off.png b/samples/PhotoEditor/res/drawable/doodle_off.png
new file mode 100644
index 0000000..5b63178
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/doodle_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/doodle_on.png b/samples/PhotoEditor/res/drawable/doodle_on.png
new file mode 100644
index 0000000..f8a7592
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/doodle_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/duotone_off.png b/samples/PhotoEditor/res/drawable/duotone_off.png
new file mode 100644
index 0000000..994e104
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/duotone_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/duotone_on.png b/samples/PhotoEditor/res/drawable/duotone_on.png
new file mode 100644
index 0000000..6f71b75
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/duotone_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/filllight_off.png b/samples/PhotoEditor/res/drawable/filllight_off.png
new file mode 100644
index 0000000..999b7f7
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/filllight_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/filllight_on.png b/samples/PhotoEditor/res/drawable/filllight_on.png
new file mode 100644
index 0000000..eff5c59
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/filllight_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/fisheye_off.png b/samples/PhotoEditor/res/drawable/fisheye_off.png
new file mode 100644
index 0000000..5fd1b93
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/fisheye_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/fisheye_on.png b/samples/PhotoEditor/res/drawable/fisheye_on.png
new file mode 100644
index 0000000..851141f
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/fisheye_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/flip_off.png b/samples/PhotoEditor/res/drawable/flip_off.png
new file mode 100644
index 0000000..cdea423
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/flip_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/flip_on.png b/samples/PhotoEditor/res/drawable/flip_on.png
new file mode 100644
index 0000000..4f5c298
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/flip_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/grain_off.png b/samples/PhotoEditor/res/drawable/grain_off.png
new file mode 100644
index 0000000..f6ff7fa
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/grain_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/grain_on.png b/samples/PhotoEditor/res/drawable/grain_on.png
new file mode 100644
index 0000000..7533af3
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/grain_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/grayscale_off.png b/samples/PhotoEditor/res/drawable/grayscale_off.png
new file mode 100644
index 0000000..3c09cd3
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/grayscale_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/grayscale_on.png b/samples/PhotoEditor/res/drawable/grayscale_on.png
new file mode 100644
index 0000000..4df7c4d
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/grayscale_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/group_header.9.png b/samples/PhotoEditor/res/drawable/group_header.9.png
new file mode 100644
index 0000000..a535678
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/group_header.9.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/highlight_off.png b/samples/PhotoEditor/res/drawable/highlight_off.png
new file mode 100644
index 0000000..1d59fb4
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/highlight_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/highlight_on.png b/samples/PhotoEditor/res/drawable/highlight_on.png
new file mode 100644
index 0000000..88c580e
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/highlight_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/icon.png b/samples/PhotoEditor/res/drawable/icon.png
new file mode 100644
index 0000000..34410f8
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/icon.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/lomoish_off.png b/samples/PhotoEditor/res/drawable/lomoish_off.png
new file mode 100644
index 0000000..ee347e6
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/lomoish_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/lomoish_on.png b/samples/PhotoEditor/res/drawable/lomoish_on.png
new file mode 100644
index 0000000..b6059ec
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/lomoish_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/negative_off.png b/samples/PhotoEditor/res/drawable/negative_off.png
new file mode 100644
index 0000000..96da62c
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/negative_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/negative_on.png b/samples/PhotoEditor/res/drawable/negative_on.png
new file mode 100644
index 0000000..cc732d9
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/negative_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/posterize_off.png b/samples/PhotoEditor/res/drawable/posterize_off.png
new file mode 100644
index 0000000..ac5384b
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/posterize_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/posterize_on.png b/samples/PhotoEditor/res/drawable/posterize_on.png
new file mode 100644
index 0000000..eed1284
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/posterize_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/quickview.png b/samples/PhotoEditor/res/drawable/quickview.png
new file mode 100644
index 0000000..cefd851
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/quickview.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/quickview_on.png b/samples/PhotoEditor/res/drawable/quickview_on.png
new file mode 100644
index 0000000..eb7c77f
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/quickview_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/redeye_off.png b/samples/PhotoEditor/res/drawable/redeye_off.png
new file mode 100644
index 0000000..ec21a96
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/redeye_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/redeye_on.png b/samples/PhotoEditor/res/drawable/redeye_on.png
new file mode 100644
index 0000000..df83f11
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/redeye_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/redo.png b/samples/PhotoEditor/res/drawable/redo.png
new file mode 100644
index 0000000..780871e
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/redo.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/rotate_off.png b/samples/PhotoEditor/res/drawable/rotate_off.png
new file mode 100644
index 0000000..153d733
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/rotate_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/rotate_on.png b/samples/PhotoEditor/res/drawable/rotate_on.png
new file mode 100644
index 0000000..c2e26c7
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/rotate_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/saturation_off.png b/samples/PhotoEditor/res/drawable/saturation_off.png
new file mode 100644
index 0000000..d151d5c
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/saturation_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/saturation_on.png b/samples/PhotoEditor/res/drawable/saturation_on.png
new file mode 100644
index 0000000..415305d
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/saturation_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/saveas.png b/samples/PhotoEditor/res/drawable/saveas.png
new file mode 100644
index 0000000..5f572ac
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/saveas.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/scale_wheel_background.png b/samples/PhotoEditor/res/drawable/scale_wheel_background.png
new file mode 100644
index 0000000..c3dc5c8
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/scale_wheel_background.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/sepia_off.png b/samples/PhotoEditor/res/drawable/sepia_off.png
new file mode 100644
index 0000000..236b990
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/sepia_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/sepia_on.png b/samples/PhotoEditor/res/drawable/sepia_on.png
new file mode 100644
index 0000000..9fb3b66
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/sepia_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/shadow_off.png b/samples/PhotoEditor/res/drawable/shadow_off.png
new file mode 100644
index 0000000..e3caa44
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/shadow_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/shadow_on.png b/samples/PhotoEditor/res/drawable/shadow_on.png
new file mode 100644
index 0000000..5d7e0da
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/shadow_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/sharpen_off.png b/samples/PhotoEditor/res/drawable/sharpen_off.png
new file mode 100644
index 0000000..89ce896
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/sharpen_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/sharpen_on.png b/samples/PhotoEditor/res/drawable/sharpen_on.png
new file mode 100644
index 0000000..dc030fa
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/sharpen_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/softfocus_off.png b/samples/PhotoEditor/res/drawable/softfocus_off.png
new file mode 100644
index 0000000..e7b63f2
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/softfocus_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/softfocus_on.png b/samples/PhotoEditor/res/drawable/softfocus_on.png
new file mode 100644
index 0000000..cfd23c5
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/softfocus_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/straighten_off.png b/samples/PhotoEditor/res/drawable/straighten_off.png
new file mode 100644
index 0000000..d558ed8
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/straighten_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/straighten_on.png b/samples/PhotoEditor/res/drawable/straighten_on.png
new file mode 100644
index 0000000..7a81448
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/straighten_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/tint_off.png b/samples/PhotoEditor/res/drawable/tint_off.png
new file mode 100644
index 0000000..6a6bb33
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/tint_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/tint_on.png b/samples/PhotoEditor/res/drawable/tint_on.png
new file mode 100644
index 0000000..9ade695
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/tint_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/undo.png b/samples/PhotoEditor/res/drawable/undo.png
new file mode 100644
index 0000000..2fa3df6
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/undo.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/vignette_off.png b/samples/PhotoEditor/res/drawable/vignette_off.png
new file mode 100644
index 0000000..e529563
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/vignette_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/vignette_on.png b/samples/PhotoEditor/res/drawable/vignette_on.png
new file mode 100644
index 0000000..e5d762d
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/vignette_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/warmify_off.png b/samples/PhotoEditor/res/drawable/warmify_off.png
new file mode 100644
index 0000000..770746a
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/warmify_off.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/warmify_on.png b/samples/PhotoEditor/res/drawable/warmify_on.png
new file mode 100644
index 0000000..443fb7a
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/warmify_on.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/wheel_knot_normal.png b/samples/PhotoEditor/res/drawable/wheel_knot_normal.png
new file mode 100644
index 0000000..5a3b27d
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/wheel_knot_normal.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/wheel_knot_pressed.png b/samples/PhotoEditor/res/drawable/wheel_knot_pressed.png
new file mode 100644
index 0000000..3b95aa5
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/wheel_knot_pressed.png
Binary files differ
diff --git a/samples/PhotoEditor/res/drawable/wheel_knot_selector.xml b/samples/PhotoEditor/res/drawable/wheel_knot_selector.xml
new file mode 100644
index 0000000..52ac39e
--- /dev/null
+++ b/samples/PhotoEditor/res/drawable/wheel_knot_selector.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="false" android:drawable="@drawable/wheel_knot_normal" />
+    <item android:state_pressed="true" android:drawable="@drawable/wheel_knot_pressed" />
+</selector>
diff --git a/samples/PhotoEditor/res/layout/artistic_effects.xml b/samples/PhotoEditor/res/layout/artistic_effects.xml
new file mode 100644
index 0000000..1d34450
--- /dev/null
+++ b/samples/PhotoEditor/res/layout/artistic_effects.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.photoeditor.EffectsGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:photoeditor="http://schemas.android.com/apk/res/com.android.photoeditor"
+    style="@style/EffectsBarLinearLayout">
+
+    <FrameLayout android:id="@+id/group_header" style="@style/GroupHeader">
+
+        <TextView android:text="@string/artistic_group" style="@style/GroupLabel"/>
+        <ImageView android:id="@+id/group_arrow" style="@style/GroupArrow"/>
+
+    </FrameLayout>
+
+    <LinearLayout android:id="@+id/grouped_effects" style="@style/GroupContent">
+
+        <LinearLayout style="@style/Effect" android:id="@+id/fisheye_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/fisheye_modes"
+                photoeditor:icons="@array/fisheye_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/fisheye"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/vignette_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/vignette_modes"
+                photoeditor:icons="@array/vignette_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/vignette"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/crossprocess_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/crossprocess_modes"
+                photoeditor:icons="@array/crossprocess_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/crossprocess"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/documentary_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/documentary_modes"
+                photoeditor:icons="@array/documentary_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/documentary"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/lomoish_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/lomoish_modes"
+                photoeditor:icons="@array/lomoish_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/lomoish"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/grain_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/grain_modes"
+                photoeditor:icons="@array/grain_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/grain"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/posterize_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/posterize_modes"
+                photoeditor:icons="@array/posterize_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/posterize"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/doodle_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/doodle_modes"
+                photoeditor:icons="@array/doodle_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/doodle"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+
+    </LinearLayout>
+</com.android.photoeditor.EffectsGroup>
diff --git a/samples/PhotoEditor/res/layout/color_effects.xml b/samples/PhotoEditor/res/layout/color_effects.xml
new file mode 100644
index 0000000..7fe0669
--- /dev/null
+++ b/samples/PhotoEditor/res/layout/color_effects.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.photoeditor.EffectsGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:photoeditor="http://schemas.android.com/apk/res/com.android.photoeditor"
+    style="@style/EffectsBarLinearLayout">
+
+    <FrameLayout android:id="@+id/group_header" style="@style/GroupHeader">
+
+        <TextView android:text="@string/color_group" style="@style/GroupLabel"/>
+        <ImageView android:id="@+id/group_arrow" style="@style/GroupArrow"/>
+
+    </FrameLayout>
+
+    <LinearLayout android:id="@+id/grouped_effects" style="@style/GroupContent">
+
+        <LinearLayout style="@style/Effect" android:id="@+id/warmify_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/warmify_modes"
+                photoeditor:icons="@array/warmify_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/warmify"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/sepia_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/sepia_modes"
+                photoeditor:icons="@array/sepia_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/sepia"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/grayscale_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/grayscale_modes"
+                photoeditor:icons="@array/grayscale_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/grayscale"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/negative_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/negative_modes"
+                photoeditor:icons="@array/negative_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/negative"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/duotone_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/duotone_modes"
+                photoeditor:icons="@array/duotone_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/duotone"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/tint_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/tint_modes"
+                photoeditor:icons="@array/tint_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/tint"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/temperature_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/temperature_modes"
+                photoeditor:icons="@array/temperature_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/temperature"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/saturation_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/saturation_modes"
+                photoeditor:icons="@array/saturation_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/saturation"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+
+    </LinearLayout>
+</com.android.photoeditor.EffectsGroup>
diff --git a/samples/PhotoEditor/res/layout/exposure_effects.xml b/samples/PhotoEditor/res/layout/exposure_effects.xml
new file mode 100644
index 0000000..b1d3476
--- /dev/null
+++ b/samples/PhotoEditor/res/layout/exposure_effects.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.photoeditor.EffectsGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:photoeditor="http://schemas.android.com/apk/res/com.android.photoeditor"
+    style="@style/EffectsBarLinearLayout">
+
+    <FrameLayout android:id="@+id/group_header" style="@style/GroupHeader">
+
+        <TextView android:text="@string/exposure_group" style="@style/GroupLabel"/>
+        <ImageView android:id="@+id/group_arrow" style="@style/GroupArrow"/>
+
+    </FrameLayout>
+
+    <LinearLayout android:id="@+id/grouped_effects" style="@style/GroupContent">
+
+        <LinearLayout style="@style/Effect" android:id="@+id/filllight_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/filllight_modes"
+                photoeditor:icons="@array/filllight_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/filllight"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/highlight_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/highlight_modes"
+                photoeditor:icons="@array/highlight_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/highlight"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/shadow_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/shadow_modes"
+                photoeditor:icons="@array/shadow_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/shadow"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/autofix_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/autofix_modes"
+                photoeditor:icons="@array/autofix_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/autofix"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+
+    </LinearLayout>
+</com.android.photoeditor.EffectsGroup>
diff --git a/samples/PhotoEditor/res/layout/fix_effects.xml b/samples/PhotoEditor/res/layout/fix_effects.xml
new file mode 100644
index 0000000..7892646
--- /dev/null
+++ b/samples/PhotoEditor/res/layout/fix_effects.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.photoeditor.EffectsGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:photoeditor="http://schemas.android.com/apk/res/com.android.photoeditor"
+    style="@style/EffectsBarLinearLayout">
+
+    <FrameLayout android:id="@+id/group_header" style="@style/GroupHeader">
+
+        <TextView android:text="@string/fix_group" style="@style/GroupLabel"/>
+        <ImageView android:id="@+id/group_arrow" style="@style/GroupArrow"/>
+
+    </FrameLayout>
+
+    <LinearLayout android:id="@+id/grouped_effects" style="@style/GroupContent">
+
+        <LinearLayout style="@style/Effect" android:id="@+id/crop_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/crop_modes"
+                photoeditor:icons="@array/crop_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/crop"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/rotate_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/rotate_modes"
+                photoeditor:icons="@array/rotate_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/rotate"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/straighten_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/straighten_modes"
+                photoeditor:icons="@array/straighten_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/straighten"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/flip_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/flip_modes"
+                photoeditor:icons="@array/flip_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/flip"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/redeye_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/redeye_modes"
+                photoeditor:icons="@array/redeye_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/redeye"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <LinearLayout style="@style/Effect" android:id="@+id/sharpen_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/sharpen_modes"
+                photoeditor:icons="@array/sharpen_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/sharpen"
+                style="@style/EffectLabel"/>
+        </LinearLayout>
+        <!--<LinearLayout style="@style/Effect" android:id="@+id/softfocus_effect">
+            <com.android.photoeditor.IconIndicator
+                android:id="@+id/effect_button"
+                style="@style/EffectIcon"
+                photoeditor:modes="@array/softfocus_modes"
+                photoeditor:icons="@array/softfocus_icons"/>
+            <TextView
+                android:id="@+id/effect_label"
+                android:text="@string/softfocus"
+                style="@style/EffectLabel"/>
+        </LinearLayout>-->
+
+    </LinearLayout>
+</com.android.photoeditor.EffectsGroup>
diff --git a/samples/PhotoEditor/res/layout/main.xml b/samples/PhotoEditor/res/layout/main.xml
new file mode 100644
index 0000000..dee7f50
--- /dev/null
+++ b/samples/PhotoEditor/res/layout/main.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.photoeditor.Toolbar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/toolbar"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <com.android.photoeditor.PhotoView
+        android:id="@+id/photo_view"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"/>
+
+    <com.android.photoeditor.actions.TouchView
+        android:id="@+id/touch_view"
+        style="@style/FullscreenToolView"/>
+    <com.android.photoeditor.actions.DoodleView
+        android:id="@+id/doodle_view"
+        style="@style/FullscreenToolView"/>
+    <com.android.photoeditor.actions.RotateView
+        android:id="@+id/rotate_view"
+        style="@style/FullscreenToolView"/>
+    <com.android.photoeditor.actions.CropView
+        android:id="@+id/crop_view"
+        style="@style/FullscreenToolView"/>
+    <com.android.photoeditor.actions.ScaleWheel
+        android:id="@+id/scale_wheel"
+        android:tag="fadeOnIdle"
+        style="@style/Wheel"/>
+    <com.android.photoeditor.actions.ColorWheel
+        android:id="@+id/color_wheel"
+        android:tag="fadeOnIdle"
+        style="@style/Wheel"/>
+
+    <com.android.photoeditor.EffectsBar
+        android:id="@+id/effects_bar"
+        android:layout_below="@+id/action_bar"
+        style="@style/EffectsBar">
+
+        <LinearLayout
+            android:id="@+id/effects_container"
+            style="@style/EffectsBarLinearLayout">
+
+            <include layout="@layout/exposure_effects"/>
+            <include layout="@layout/fix_effects"/>
+            <include layout="@layout/artistic_effects"/>
+            <include layout="@layout/color_effects"/>
+        </LinearLayout>
+    </com.android.photoeditor.EffectsBar>
+
+    <com.android.photoeditor.ActionBar
+        android:id="@+id/action_bar"
+        android:tag="fadeOnIdle"
+        style="@style/ActionBar">
+
+        <RelativeLayout style="@style/ActionBarTranslucent">
+            <LinearLayout style="@style/ActionBarLinearLayout">
+
+                <LinearLayout
+                    android:id="@+id/action_bar_back"
+                    style="@style/ActionBarBackLinearLayout">
+                    <ImageView style="@style/ActionBarArrow"/>
+                    <ImageView style="@style/ActionBarIcon"/>
+                </LinearLayout>
+
+                <TextView android:id="@+id/action_effect_name" style="@style/ActionBarText"/>
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/ActionBarLinearLayout"
+                android:layout_alignParentRight="true">
+
+                <ImageButton
+                    android:id="@+id/save_button"
+                    style="@style/ActionButton"
+                    android:src="@drawable/saveas"/>
+                <ImageButton
+                    android:id="@+id/undo_button"
+                    style="@style/ActionButton"
+                    android:src="@drawable/undo"/>
+                <ImageButton
+                    android:id="@+id/redo_button"
+                    style="@style/ActionButton"
+                    android:src="@drawable/redo"/>
+                <ImageButton
+                    android:id="@+id/quickview_button"
+                    style="@style/RightmostActionButton"
+                    android:src="@drawable/quickview"/>
+            </LinearLayout>
+        </RelativeLayout>
+
+        <ImageButton
+            android:id="@+id/quickview_on_button"
+            style="@style/RightmostActionButton"
+            android:layout_gravity="center_vertical|right"
+            android:src="@drawable/quickview_on"/>
+
+    </com.android.photoeditor.ActionBar>
+</com.android.photoeditor.Toolbar>
diff --git a/samples/PhotoEditor/res/values-ar/strings.xml b/samples/PhotoEditor/res/values-ar/strings.xml
new file mode 100644
index 0000000..4405989
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ar/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"تعذر تحميل الصورة للتعديل"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"حدث خطأ أثناء الحفظ"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"تم حفظ <xliff:g id="FILENAME">%s</xliff:g>"</string>
+    <string name="save_photo" msgid="3125109368779997862">"هل تريد حفظ الصورة التي تم تعديلها؟"</string>
+    <string name="yes" msgid="5402582493291792293">"نعم"</string>
+    <string name="no" msgid="5595408018304861875">"لا"</string>
+    <string name="cancel" msgid="6286688759430767307">"إلغاء"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"صور معدلة"</string>
+    <string name="artistic_group" msgid="573091775825402562">"فني"</string>
+    <string name="color_group" msgid="3390757893475444688">"اللون"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"التعرض للضوء"</string>
+    <string name="fix_group" msgid="971450155810247383">"إصلاح"</string>
+    <string name="autofix" msgid="1223859191856172755">"ضربة حظ"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"معالجة متقاطعة"</string>
+    <string name="documentary" msgid="50396326708699797">"وثائقي"</string>
+    <string name="doodle" msgid="1686409894518940990">"رسومات مبتكرة"</string>
+    <string name="duotone" msgid="8145893940788467106">"درجة لونين"</string>
+    <string name="filllight" msgid="2644989991700022526">"إضاءة تكميلية"</string>
+    <string name="fisheye" msgid="6037488646928998921">"عين سمكة"</string>
+    <string name="flip" msgid="2357692401826287480">"عكس"</string>
+    <string name="grain" msgid="7487585304579789098">"تحبب الفيلم"</string>
+    <string name="grayscale" msgid="615169770671286699">"تدرج الرمادي"</string>
+    <string name="highlight" msgid="3902653944386623972">"لمحات"</string>
+    <string name="lomoish" msgid="7793824845892532976">"متعلق بـ Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"سلبي"</string>
+    <string name="posterize" msgid="4139212359561383385">"تتالٍ"</string>
+    <string name="redeye" msgid="4958448806369928239">"العين الحمراء"</string>
+    <string name="rotate" msgid="6607597269792373083">"تدوير"</string>
+    <string name="saturation" msgid="8621322012271169931">"تشبع اللون"</string>
+    <string name="sepia" msgid="7978093531824705601">"بني داكن"</string>
+    <string name="shadow" msgid="8235188588101973090">"ظلال"</string>
+    <string name="sharpen" msgid="8449662378104403230">"زيادة الحدة"</string>
+    <string name="softfocus" msgid="6334228862566408303">"تركيز ضعيف"</string>
+    <string name="straighten" msgid="5217801513491493491">"تسوية"</string>
+    <string name="temperature" msgid="3589958696423284897">"درجة الحرارة"</string>
+    <string name="tint" msgid="154435943863418434">"تلوين خفيف"</string>
+    <string name="vignette" msgid="7648125924662648282">"نقوش صورة نصفية"</string>
+    <string name="warmify" msgid="2404474974251014984">"زيادة دفء اللون"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"اسحب الصورة لإنشاء رسومات مبتكرة"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"اسحب الصورة لقلبها"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"انقر لإزالة الأعين الحمراء"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"اسحب الصورة لتدويرها"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"اسحب الصورة لتسويتها"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-bg/strings.xml b/samples/PhotoEditor/res/values-bg/strings.xml
new file mode 100644
index 0000000..f28f7d7
--- /dev/null
+++ b/samples/PhotoEditor/res/values-bg/strings.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Снимката не може да бъде заредена за редактиране"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Възникна проблем при запазването"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"„<xliff:g id="FILENAME">%s</xliff:g>“ е запазено"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Да се запази ли редактираната снимка?"</string>
+    <string name="yes" msgid="5402582493291792293">"Да"</string>
+    <string name="no" msgid="5595408018304861875">"Нe"</string>
+    <string name="cancel" msgid="6286688759430767307">"Отказ"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Редактирани"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Художеств."</string>
+    <string name="color_group" msgid="3390757893475444688">"Цвят"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Експонация"</string>
+    <string name="fix_group" msgid="971450155810247383">"Корекция"</string>
+    <string name="autofix" msgid="1223859191856172755">"С късмет"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Кроспроцес"</string>
+    <string name="documentary" msgid="50396326708699797">"Документален филм"</string>
+    <string name="doodle" msgid="1686409894518940990">"Рисунка"</string>
+    <string name="duotone" msgid="8145893940788467106">"В два цвята"</string>
+    <string name="filllight" msgid="2644989991700022526">"Запълв. светл."</string>
+    <string name="fisheye" msgid="6037488646928998921">"Рибешко око"</string>
+    <string name="flip" msgid="2357692401826287480">"Обръщане"</string>
+    <string name="grain" msgid="7487585304579789098">"Зърнистост"</string>
+    <string name="grayscale" msgid="615169770671286699">"Сива скала"</string>
+    <string name="highlight" msgid="3902653944386623972">"Светли участъци"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Като „Ломо“"</string>
+    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
+    <string name="posterize" msgid="4139212359561383385">"Плакат"</string>
+    <string name="redeye" msgid="4958448806369928239">"Червени очи"</string>
+    <string name="rotate" msgid="6607597269792373083">"Завъртане"</string>
+    <string name="saturation" msgid="8621322012271169931">"Насищане"</string>
+    <string name="sepia" msgid="7978093531824705601">"Сепия"</string>
+    <string name="shadow" msgid="8235188588101973090">"Тъмни участъци"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Отчетливост"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Мек фокус"</string>
+    <string name="straighten" msgid="5217801513491493491">"Изправяне"</string>
+    <string name="temperature" msgid="3589958696423284897">"Температура"</string>
+    <string name="tint" msgid="154435943863418434">"Нюансиране"</string>
+    <string name="vignette" msgid="7648125924662648282">"Винетиране"</string>
+    <string name="warmify" msgid="2404474974251014984">"Затопляне"</string>
+    <!-- no translation found for doodle_tooltip (2902117272374362915) -->
+    <skip />
+    <!-- no translation found for flip_tooltip (2700943256714731737) -->
+    <skip />
+    <!-- no translation found for redeye_tooltip (9112774042113471358) -->
+    <skip />
+    <!-- no translation found for rotate_tooltip (7008602969130734229) -->
+    <skip />
+    <!-- no translation found for straighten_tooltip (4846317027139212339) -->
+    <skip />
+</resources>
diff --git a/samples/PhotoEditor/res/values-ca/strings.xml b/samples/PhotoEditor/res/values-ca/strings.xml
new file mode 100644
index 0000000..0dcb2e6
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ca/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Estudi de fotografia"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"La foto no es pot carregar per editar-la"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"S\'ha produït un problema en desar"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> desada"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Vols desar la foto editada?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sí"</string>
+    <string name="no" msgid="5595408018304861875">"No"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancel·la"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editades"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artístics"</string>
+    <string name="color_group" msgid="3390757893475444688">"Color"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposició"</string>
+    <string name="fix_group" msgid="971450155810247383">"Correcció"</string>
+    <string name="autofix" msgid="1223859191856172755">"Correcc. autom."</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Procés creuat"</string>
+    <string name="documentary" msgid="50396326708699797">"Documental"</string>
+    <string name="doodle" msgid="1686409894518940990">"Gargots"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dos tons"</string>
+    <string name="filllight" msgid="2644989991700022526">"Retroil·lumin."</string>
+    <string name="fisheye" msgid="6037488646928998921">"Ull de peix"</string>
+    <string name="flip" msgid="2357692401826287480">"Inversió"</string>
+    <string name="grain" msgid="7487585304579789098">"Gra pel·lícula"</string>
+    <string name="grayscale" msgid="615169770671286699">"Escala grisos"</string>
+    <string name="highlight" msgid="3902653944386623972">"Realçaments"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Estil Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatiu"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterització"</string>
+    <string name="redeye" msgid="4958448806369928239">"Ulls vermells"</string>
+    <string name="rotate" msgid="6607597269792373083">"Gir"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturació"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sèpia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Ombres"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Nitidesa"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Focus suau"</string>
+    <string name="straighten" msgid="5217801513491493491">"Redreçament"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tint"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinyeta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Calidesa"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Arrossega el cursor sobre la foto per dibuixar"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Arrossega la foto per invertir-la"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Toca per eliminar els ulls vermells"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrossega la foto per girar-la"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrossega la foto per redreçar-la"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-cs/strings.xml b/samples/PhotoEditor/res/values-cs/strings.xml
new file mode 100644
index 0000000..df0b474
--- /dev/null
+++ b/samples/PhotoEditor/res/values-cs/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Fotografii nelze načíst k úpravám"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Při ukládání došlo k problému"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Fotografie <xliff:g id="FILENAME">%s</xliff:g> byla uložena"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Chcete upravenou fotografii uložit?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ano"</string>
+    <string name="no" msgid="5595408018304861875">"Ne"</string>
+    <string name="cancel" msgid="6286688759430767307">"Zrušit"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Upraveno"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Umělecké"</string>
+    <string name="color_group" msgid="3390757893475444688">"Barva"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Expozice"</string>
+    <string name="fix_group" msgid="971450155810247383">"Korekce"</string>
+    <string name="autofix" msgid="1223859191856172755">"Zkusím štěstí"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Křížové zpracování"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
+    <string name="doodle" msgid="1686409894518940990">"Kreslení"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
+    <string name="filllight" msgid="2644989991700022526">"Zadní osvětlení"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Rybí oko"</string>
+    <string name="flip" msgid="2357692401826287480">"Převrácení"</string>
+    <string name="grain" msgid="7487585304579789098">"Zrnitý film"</string>
+    <string name="grayscale" msgid="615169770671286699">"Stupně šedi"</string>
+    <string name="highlight" msgid="3902653944386623972">"Přisvícení"</string>
+    <string name="lomoish" msgid="7793824845892532976">"LOMO"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizace"</string>
+    <string name="redeye" msgid="4958448806369928239">"Červené oči"</string>
+    <string name="rotate" msgid="6607597269792373083">"Otočení"</string>
+    <string name="saturation" msgid="8621322012271169931">"Sytost"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sépiové barvy"</string>
+    <string name="shadow" msgid="8235188588101973090">"Stíny"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Zaostření"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Jemné rozostření"</string>
+    <string name="straighten" msgid="5217801513491493491">"Vyrovnání"</string>
+    <string name="temperature" msgid="3589958696423284897">"Teplota"</string>
+    <string name="tint" msgid="154435943863418434">"Tónování"</string>
+    <string name="vignette" msgid="7648125924662648282">"Medailonek"</string>
+    <string name="warmify" msgid="2404474974251014984">"Teplejší barvy"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii můžete kreslit prstem"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Táhnutím prstem fotografii převrátíte"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Klepnutím odstraníte červené oči"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Táhnutím prstem fotografii otočíte"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Táhnutím prstem fotografii narovnáte"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-da/strings.xml b/samples/PhotoEditor/res/values-da/strings.xml
new file mode 100644
index 0000000..0dd390f
--- /dev/null
+++ b/samples/PhotoEditor/res/values-da/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Fotostudie"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Billedet kan ikke indlæses til redigering"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Der opstod et problem ved lagring"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> er gemt"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Vil du gemme det redigerede billede?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ja"</string>
+    <string name="no" msgid="5595408018304861875">"Nej"</string>
+    <string name="cancel" msgid="6286688759430767307">"Annuller"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigeret"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Kunstnerisk"</string>
+    <string name="color_group" msgid="3390757893475444688">"Farve"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Eksponering"</string>
+    <string name="fix_group" msgid="971450155810247383">"Ret"</string>
+    <string name="autofix" msgid="1223859191856172755">"Prøv lykken"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Krydsfremkald"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentar"</string>
+    <string name="doodle" msgid="1686409894518940990">"Krusedulle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Tofarvet"</string>
+    <string name="filllight" msgid="2644989991700022526">"Oplys mørke"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fiskeøje"</string>
+    <string name="flip" msgid="2357692401826287480">"Vend"</string>
+    <string name="grain" msgid="7487585304579789098">"Filmkorn"</string>
+    <string name="grayscale" msgid="615169770671286699">"Gråtoner"</string>
+    <string name="highlight" msgid="3902653944386623972">"Fremhævning"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-effekt"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterisering"</string>
+    <string name="redeye" msgid="4958448806369928239">"Røde øjne"</string>
+    <string name="rotate" msgid="6607597269792373083">"Roter"</string>
+    <string name="saturation" msgid="8621322012271169931">"Mætning"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Skygger"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Gør skarpere"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Blødt fokus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Ret op"</string>
+    <string name="temperature" msgid="3589958696423284897">"Farvetemperatur"</string>
+    <string name="tint" msgid="154435943863418434">"Nuance"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignet"</string>
+    <string name="warmify" msgid="2404474974251014984">"Gør varmere"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på billedet for at lave kruseduller"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Træk billedet for at vende det"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tryk for at fjerne røde øjne"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Træk billedet for at rotere det"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Træk i billedet for at rette det op"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-de/strings.xml b/samples/PhotoEditor/res/values-de/strings.xml
new file mode 100644
index 0000000..0e5df5f
--- /dev/null
+++ b/samples/PhotoEditor/res/values-de/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Laden des Fotos zum Bearbeiten nicht möglich."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Beim Speichern ist ein Problem aufgetreten."</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> gespeichert"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Bearbeitetes Foto speichern?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ja"</string>
+    <string name="no" msgid="5595408018304861875">"Nein"</string>
+    <string name="cancel" msgid="6286688759430767307">"Abbrechen"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Bearbeitet"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Kunstvoll"</string>
+    <string name="color_group" msgid="3390757893475444688">"Farbe"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Belichtung"</string>
+    <string name="fix_group" msgid="971450155810247383">"Korrektur"</string>
+    <string name="autofix" msgid="1223859191856172755">"Gute Stimmung"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Cross-Entwickl."</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentation"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duoton"</string>
+    <string name="filllight" msgid="2644989991700022526">"Aufhellen"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fischauge"</string>
+    <string name="flip" msgid="2357692401826287480">"Spiegeln"</string>
+    <string name="grain" msgid="7487585304579789098">"Filmkörnung"</string>
+    <string name="grayscale" msgid="615169770671286699">"Graustufen"</string>
+    <string name="highlight" msgid="3902653944386623972">"Highlights"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomoesk"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Tontrennung"</string>
+    <string name="redeye" msgid="4958448806369928239">"Rote Augen"</string>
+    <string name="rotate" msgid="6607597269792373083">"Drehen"</string>
+    <string name="saturation" msgid="8621322012271169931">"Sättigung"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Schatten"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Scharf stellen"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Weichzeichnen"</string>
+    <string name="straighten" msgid="5217801513491493491">"Ausrichten"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatur"</string>
+    <string name="tint" msgid="154435943863418434">"Färbung"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignettierung"</string>
+    <string name="warmify" msgid="2404474974251014984">"Wärmer gestalten"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Zum Erstellen eines Doodles auf dem Foto zeichnen"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Foto ziehen, um es umzudrehen"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Zum Entfernen roter Augen tippen"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Foto ziehen, um es zu drehen"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Foto ziehen, um es zu strecken"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-el/strings.xml b/samples/PhotoEditor/res/values-el/strings.xml
new file mode 100644
index 0000000..30651ba
--- /dev/null
+++ b/samples/PhotoEditor/res/values-el/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Δεν είναι δυνατή η φόρτωση της φωτογραφίας για επεξεργασία"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Παρουσιάστηκε πρόβλημα κατά την αποθήκευση"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Το <xliff:g id="FILENAME">%s</xliff:g> αποθηκεύτηκε"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Αποθήκευση επεξεργασμένης φωτογραφίας;"</string>
+    <string name="yes" msgid="5402582493291792293">"Ναι"</string>
+    <string name="no" msgid="5595408018304861875">"Όχι"</string>
+    <string name="cancel" msgid="6286688759430767307">"Ακύρωση"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Επεξεργασμ."</string>
+    <string name="artistic_group" msgid="573091775825402562">"Καλλιτεχν."</string>
+    <string name="color_group" msgid="3390757893475444688">"Χρώμα"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Έκθεση"</string>
+    <string name="fix_group" msgid="971450155810247383">"Διόρθωση"</string>
+    <string name="autofix" msgid="1223859191856172755">"Νιώθω τυχερός"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Cross Process"</string>
+    <string name="documentary" msgid="50396326708699797">"Ντοκιμαντέρ"</string>
+    <string name="doodle" msgid="1686409894518940990">"Σκετσάκι"</string>
+    <string name="duotone" msgid="8145893940788467106">"Δύο τόνοι"</string>
+    <string name="filllight" msgid="2644989991700022526">"Γέμισμα με φως"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Αναστροφή"</string>
+    <string name="grain" msgid="7487585304579789098">"Κόκκος φιλμ"</string>
+    <string name="grayscale" msgid="615169770671286699">"Κλίμακα του γκρι"</string>
+    <string name="highlight" msgid="3902653944386623972">"Επισημάνσεις"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Φίλτρο lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Αρνητικό"</string>
+    <string name="posterize" msgid="4139212359561383385">"Ποστεροποίηση"</string>
+    <string name="redeye" msgid="4958448806369928239">"Κόκκινα μάτια"</string>
+    <string name="rotate" msgid="6607597269792373083">"Περιστροφή"</string>
+    <string name="saturation" msgid="8621322012271169931">"Κορεσμός"</string>
+    <string name="sepia" msgid="7978093531824705601">"Σέπια"</string>
+    <string name="shadow" msgid="8235188588101973090">"Σκιές"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Όξυνση"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Απαλή εστίαση"</string>
+    <string name="straighten" msgid="5217801513491493491">"Ευθυγράμμιση"</string>
+    <string name="temperature" msgid="3589958696423284897">"Θερμοκρασία"</string>
+    <string name="tint" msgid="154435943863418434">"Απόχρωση"</string>
+    <string name="vignette" msgid="7648125924662648282">"Βινιετάρισμα"</string>
+    <string name="warmify" msgid="2404474974251014984">"Ζεστή απόχρωση"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Σχεδιάστε στη φωτογραφία για να δημιουργήσετε ένα σκετσάκι"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Σύρετε τη φωτογραφία για να την αναστρέψετε"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Πατήστε για να αφαιρέσετε τα κόκκινα μάτια"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Σύρετε τη φωτογραφία για να την περιστρέψετε"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Σύρετε τη φωτογραφία για να την ευθυγραμμίσετε"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-en-rGB/strings.xml b/samples/PhotoEditor/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..14df5a7
--- /dev/null
+++ b/samples/PhotoEditor/res/values-en-rGB/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Cannot load the photo for editing"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"A problem occurred during saving"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> saved"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Save edited photo?"</string>
+    <string name="yes" msgid="5402582493291792293">"Yes"</string>
+    <string name="no" msgid="5595408018304861875">"No"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancel"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Edited"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistic"</string>
+    <string name="color_group" msgid="3390757893475444688">"Colour"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposure"</string>
+    <string name="fix_group" msgid="971450155810247383">"Fix"</string>
+    <string name="autofix" msgid="1223859191856172755">"Feel Lucky"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Cross-Process"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentary"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
+    <string name="filllight" msgid="2644989991700022526">"Fill Light"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Flip"</string>
+    <string name="grain" msgid="7487585304579789098">"Film Grain"</string>
+    <string name="grayscale" msgid="615169770671286699">"Grey Scale"</string>
+    <string name="highlight" msgid="3902653944386623972">"Highlights"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-ish"</string>
+    <string name="negative" msgid="1985508917342811252">"Negative"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterise"</string>
+    <string name="redeye" msgid="4958448806369928239">"Red Eye"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotate"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Shadows"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Sharpen"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Soft Focus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Straighten"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperature"</string>
+    <string name="tint" msgid="154435943863418434">"Tint"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
+    <string name="warmify" msgid="2404474974251014984">"Warmify"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Draw on photo to doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Drag photo to flip it"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tap to remove red eyes"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Drag photo to rotate it"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Drag photo to straighten it"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-es-rUS/strings.xml b/samples/PhotoEditor/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..02c54ef
--- /dev/null
+++ b/samples/PhotoEditor/res/values-es-rUS/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Estudio de fotografía"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"No se puede cargar la foto para editarla"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Se produjo un problema al guardar"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> guardado"</string>
+    <string name="save_photo" msgid="3125109368779997862">"¿Guardar foto editada?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sí"</string>
+    <string name="no" msgid="5595408018304861875">"No"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancelar"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editadas"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artísticos"</string>
+    <string name="color_group" msgid="3390757893475444688">"Color"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposición."</string>
+    <string name="fix_group" msgid="971450155810247383">"Con arreglos"</string>
+    <string name="autofix" msgid="1223859191856172755">"Me siento con suerte"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proceso cruzado"</string>
+    <string name="documentary" msgid="50396326708699797">"Documental"</string>
+    <string name="doodle" msgid="1686409894518940990">"Garabatear"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dos tonos"</string>
+    <string name="filllight" msgid="2644989991700022526">"Aumentar brillo"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Ojo de pez"</string>
+    <string name="flip" msgid="2357692401826287480">"Cambiar"</string>
+    <string name="grain" msgid="7487585304579789098">"Grano de la película"</string>
+    <string name="grayscale" msgid="615169770671286699">"Escala de grises"</string>
+    <string name="highlight" msgid="3902653944386623972">"Destacadas"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Estilo Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"negativo"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizar"</string>
+    <string name="redeye" msgid="4958448806369928239">"Ojos rojos"</string>
+    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturación"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Mejorar nitidez"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Desenfocar"</string>
+    <string name="straighten" msgid="5217801513491493491">"Enderezar"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Matiz"</string>
+    <string name="vignette" msgid="7648125924662648282">"Viñeta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Más calidez"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darla vuelta"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Toca para eliminar los ojos rojos"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para hacerla girar"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-es/strings.xml b/samples/PhotoEditor/res/values-es/strings.xml
new file mode 100644
index 0000000..08b808e
--- /dev/null
+++ b/samples/PhotoEditor/res/values-es/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"No se puede cargar la foto para editarla."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Error al guardar"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> guardado"</string>
+    <string name="save_photo" msgid="3125109368779997862">"¿Quieres guardar la foto editada?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sí"</string>
+    <string name="no" msgid="5595408018304861875">"No"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancelar"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editadas"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artísticos"</string>
+    <string name="color_group" msgid="3390757893475444688">"Color"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposición"</string>
+    <string name="fix_group" msgid="971450155810247383">"Fijos"</string>
+    <string name="autofix" msgid="1223859191856172755">"Con suerte"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proceso cruzado"</string>
+    <string name="documentary" msgid="50396326708699797">"Documental"</string>
+    <string name="doodle" msgid="1686409894518940990">"Garabatos"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dos tonos"</string>
+    <string name="filllight" msgid="2644989991700022526">"Luz de relleno"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Ojo de pez"</string>
+    <string name="flip" msgid="2357692401826287480">"Dar la vuelta"</string>
+    <string name="grain" msgid="7487585304579789098">"Grano de película"</string>
+    <string name="grayscale" msgid="615169770671286699">"Escala de grises"</string>
+    <string name="highlight" msgid="3902653944386623972">"Reflejos"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Estilo lomográfico"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizar"</string>
+    <string name="redeye" msgid="4958448806369928239">"Ojos rojos"</string>
+    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturación"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Enfocar"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Desenfocar"</string>
+    <string name="straighten" msgid="5217801513491493491">"Enderezar"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tinte"</string>
+    <string name="vignette" msgid="7648125924662648282">"Viñeta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Más calidez"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darle la vuelta"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Toca para eliminar los ojos rojos"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para girarla"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-fa/strings.xml b/samples/PhotoEditor/res/values-fa/strings.xml
new file mode 100644
index 0000000..b1ce3f2
--- /dev/null
+++ b/samples/PhotoEditor/res/values-fa/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"بارگیری عکس برای ویرایش ممکن نیست"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"هنگام ذخیره کردن خطایی رخ داد"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> ذخیره شد"</string>
+    <string name="save_photo" msgid="3125109368779997862">"عکس ویرایش شده ذخیره شود؟"</string>
+    <string name="yes" msgid="5402582493291792293">"بله"</string>
+    <string name="no" msgid="5595408018304861875">"خیر"</string>
+    <string name="cancel" msgid="6286688759430767307">"لغو"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"ویرایش شده"</string>
+    <string name="artistic_group" msgid="573091775825402562">"هنرمندانه"</string>
+    <string name="color_group" msgid="3390757893475444688">"رنگ"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"نوردهی"</string>
+    <string name="fix_group" msgid="971450155810247383">"ثابت"</string>
+    <string name="autofix" msgid="1223859191856172755">"احساس خوشبختی"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"فرآیند همگذاری"</string>
+    <string name="documentary" msgid="50396326708699797">"مستند"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"دو تن رنگ"</string>
+    <string name="filllight" msgid="2644989991700022526">"پر کردن نور"</string>
+    <string name="fisheye" msgid="6037488646928998921">"فیش آی"</string>
+    <string name="flip" msgid="2357692401826287480">"برعکس کردن"</string>
+    <string name="grain" msgid="7487585304579789098">"بافت فیلم"</string>
+    <string name="grayscale" msgid="615169770671286699">"طیف خاکستری"</string>
+    <string name="highlight" msgid="3902653944386623972">"قسمت های برجسته"</string>
+    <string name="lomoish" msgid="7793824845892532976">"شبیه Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"نگاتیو"</string>
+    <string name="posterize" msgid="4139212359561383385">"پوستری کردن"</string>
+    <string name="redeye" msgid="4958448806369928239">"قرمزی چشم"</string>
+    <string name="rotate" msgid="6607597269792373083">"چرخش"</string>
+    <string name="saturation" msgid="8621322012271169931">"اشباع"</string>
+    <string name="sepia" msgid="7978093531824705601">"سپیا"</string>
+    <string name="shadow" msgid="8235188588101973090">"سایه ها"</string>
+    <string name="sharpen" msgid="8449662378104403230">"وضوح"</string>
+    <string name="softfocus" msgid="6334228862566408303">"فوکوس نرم"</string>
+    <string name="straighten" msgid="5217801513491493491">"صاف کردن"</string>
+    <string name="temperature" msgid="3589958696423284897">"دما"</string>
+    <string name="tint" msgid="154435943863418434">"ته رنگ"</string>
+    <string name="vignette" msgid="7648125924662648282">"وینت"</string>
+    <string name="warmify" msgid="2404474974251014984">"گرم کردن"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"برای doodle کردن روی عکس رسم کنید"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"برای وارونه کردن عکس آن را بکشید"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"برای حذف قرمزی چشم ضربه بزنید"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"برای چرخاندن عکس آن را بکشید"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"برای صاف کردن عکس آن را بکشید"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-fi/strings.xml b/samples/PhotoEditor/res/values-fi/strings.xml
new file mode 100644
index 0000000..4afa2d8
--- /dev/null
+++ b/samples/PhotoEditor/res/values-fi/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Kuvaa ei voi ladata muokattavaksi"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Ongelma tallennuksessa"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> tallennettu"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Tallenna muokattu valokuva?"</string>
+    <string name="yes" msgid="5402582493291792293">"Kyllä"</string>
+    <string name="no" msgid="5595408018304861875">"Ei"</string>
+    <string name="cancel" msgid="6286688759430767307">"Peruuta"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Muokattu"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Taiteellinen"</string>
+    <string name="color_group" msgid="3390757893475444688">"Väri"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Valotus"</string>
+    <string name="fix_group" msgid="971450155810247383">"Korjaus"</string>
+    <string name="autofix" msgid="1223859191856172755">"Kokeilen onneani"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Ristiinkäsittely"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentti"</string>
+    <string name="doodle" msgid="1686409894518940990">"Piirtely"</string>
+    <string name="duotone" msgid="8145893940788467106">"Kaksoissävy"</string>
+    <string name="filllight" msgid="2644989991700022526">"Täytä vaaleat kohdat"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Kalansilmä"</string>
+    <string name="flip" msgid="2357692401826287480">"Käännä"</string>
+    <string name="grain" msgid="7487585304579789098">"Filmin rakeisuus"</string>
+    <string name="grayscale" msgid="615169770671286699">"Harmaasävy"</string>
+    <string name="highlight" msgid="3902653944386623972">"Kohokohdat"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomomainen"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatiivinen"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterisaatio"</string>
+    <string name="redeye" msgid="4958448806369928239">"Punasilmäisyys"</string>
+    <string name="rotate" msgid="6607597269792373083">"Kierrä"</string>
+    <string name="saturation" msgid="8621322012271169931">"Värikylläisyys"</string>
+    <string name="sepia" msgid="7978093531824705601">"Seepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Varjostus"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Tarkennus"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Pehmeäpiirto"</string>
+    <string name="straighten" msgid="5217801513491493491">"Suorista"</string>
+    <string name="temperature" msgid="3589958696423284897">"Lämpötila"</string>
+    <string name="tint" msgid="154435943863418434">"Sävytys"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinjetti"</string>
+    <string name="warmify" msgid="2404474974251014984">"Lämmitä sävyt"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Piirrä valokuvaan siirtämällä osoitin sen päälle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Käännä kuva vetämällä"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Poista punaiset silmät napauttamalla"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Käännä valokuvaa vetämällä"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Suorista valokuva vetämällä"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-fr/strings.xml b/samples/PhotoEditor/res/values-fr/strings.xml
new file mode 100644
index 0000000..be8d971
--- /dev/null
+++ b/samples/PhotoEditor/res/values-fr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Impossible de charger la photo pour la retoucher."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Un problème est survenu lors de l\'enregistrement."</string>
+    <string name="photo_saved" msgid="5093564498138145422">"\"<xliff:g id="FILENAME">%s</xliff:g>\" enregistré"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Enregistrer photo retouchée ?"</string>
+    <string name="yes" msgid="5402582493291792293">"Oui"</string>
+    <string name="no" msgid="5595408018304861875">"Non"</string>
+    <string name="cancel" msgid="6286688759430767307">"Annuler"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Modifiées"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistique"</string>
+    <string name="color_group" msgid="3390757893475444688">"Couleur"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposition"</string>
+    <string name="fix_group" msgid="971450155810247383">"Retouches"</string>
+    <string name="autofix" msgid="1223859191856172755">"J\'ai de la chance"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Traitement croisé"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentaire"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Deux tons"</string>
+    <string name="filllight" msgid="2644989991700022526">"Éclairage d\'appoint"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Objectif fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Retourner"</string>
+    <string name="grain" msgid="7487585304579789098">"Grain"</string>
+    <string name="grayscale" msgid="615169770671286699">"Nuances de gris"</string>
+    <string name="highlight" msgid="3902653944386623972">"Reflets"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Effet Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Négatif"</string>
+    <string name="posterize" msgid="4139212359561383385">"Postérisation"</string>
+    <string name="redeye" msgid="4958448806369928239">"Yeux rouges"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotation"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Ombres"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Netteté"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Flou artistique"</string>
+    <string name="straighten" msgid="5217801513491493491">"Redresser"</string>
+    <string name="temperature" msgid="3589958696423284897">"Température"</string>
+    <string name="tint" msgid="154435943863418434">"Coloration"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignetage"</string>
+    <string name="warmify" msgid="2404474974251014984">"Couleurs chaudes"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Dessiner sur la photo pour gribouiller"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Faire glisser la photo pour la retourner"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Appuyer pour supprimer les yeux rouges"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Faire glisser la photo pour la faire pivoter"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Faire glisser la photo pour la redresser"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-hr/strings.xml b/samples/PhotoEditor/res/values-hr/strings.xml
new file mode 100644
index 0000000..bfbe0ea
--- /dev/null
+++ b/samples/PhotoEditor/res/values-hr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Foto studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Ne mogu učitati fotografiju za uređivanje"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Došlo je do problema tijekom spremanja"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> spremljena"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Spremiti uređenu fotografiju?"</string>
+    <string name="yes" msgid="5402582493291792293">"Da"</string>
+    <string name="no" msgid="5595408018304861875">"Ne"</string>
+    <string name="cancel" msgid="6286688759430767307">"Odustani"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Uređeno"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Umjetnički"</string>
+    <string name="color_group" msgid="3390757893475444688">"Boja"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Ekspozicija"</string>
+    <string name="fix_group" msgid="971450155810247383">"Popravi"</string>
+    <string name="autofix" msgid="1223859191856172755">"Sretan"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Pomak boja"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentarni"</string>
+    <string name="doodle" msgid="1686409894518940990">"Škrabotine"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duo-ton"</string>
+    <string name="filllight" msgid="2644989991700022526">"Ispuni svjetlom"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Riblje oko"</string>
+    <string name="flip" msgid="2357692401826287480">"Okreni"</string>
+    <string name="grain" msgid="7487585304579789098">"Zrnatost filma"</string>
+    <string name="grayscale" msgid="615169770671286699">"Nijanse sive"</string>
+    <string name="highlight" msgid="3902653944386623972">"Isticanja"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomografski"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativan"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizacija"</string>
+    <string name="redeye" msgid="4958448806369928239">"Crvene oči"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotiraj"</string>
+    <string name="saturation" msgid="8621322012271169931">"Zasićenje"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepija"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sjenčanje"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Izoštri"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Mekani fokus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Poravnaj"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Svjetlija nijansa"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinjeta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Stavi topliju nijansu"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Crtanje na fotografiju za doodle logotip"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Povucite sliku da biste je preokrenuli"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Dotaknite za uklanjanje crvenih očiju"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Povucite fotografiju da biste je rotirali"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Povucite fotografiju da biste je izravnali"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-hu/strings.xml b/samples/PhotoEditor/res/values-hu/strings.xml
new file mode 100644
index 0000000..3dfc431
--- /dev/null
+++ b/samples/PhotoEditor/res/values-hu/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"A fotó nem tölthető be szerkesztésre"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Hiba történt mentés közben"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> mentve"</string>
+    <string name="save_photo" msgid="3125109368779997862">"A szerkesztett kép mentése?"</string>
+    <string name="yes" msgid="5402582493291792293">"Igen"</string>
+    <string name="no" msgid="5595408018304861875">"Nem"</string>
+    <string name="cancel" msgid="6286688759430767307">"Mégse"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Szerkesztve"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Művészi"</string>
+    <string name="color_group" msgid="3390757893475444688">"Szín"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Expozíció"</string>
+    <string name="fix_group" msgid="971450155810247383">"Javítás"</string>
+    <string name="autofix" msgid="1223859191856172755">"Jó napom van"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Áttűnés"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentumfilm"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Kéttónusú"</string>
+    <string name="filllight" msgid="2644989991700022526">"Háttérfény"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Halszem"</string>
+    <string name="flip" msgid="2357692401826287480">"Tükrözés"</string>
+    <string name="grain" msgid="7487585304579789098">"Filmszemcse"</string>
+    <string name="grayscale" msgid="615169770671286699">"Szürkeárnyalat"</string>
+    <string name="highlight" msgid="3902653944386623972">"Kiemelt részek"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-hatás"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatív"</string>
+    <string name="posterize" msgid="4139212359561383385">"Poszterizálás"</string>
+    <string name="redeye" msgid="4958448806369928239">"Vörösszem"</string>
+    <string name="rotate" msgid="6607597269792373083">"Forgatás"</string>
+    <string name="saturation" msgid="8621322012271169931">"Telítettség"</string>
+    <string name="sepia" msgid="7978093531824705601">"Szépia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Árnyékok"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Élesítés"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Lágy fókusz"</string>
+    <string name="straighten" msgid="5217801513491493491">"Kiegyenesítés"</string>
+    <string name="temperature" msgid="3589958696423284897">"Hőmérséklet"</string>
+    <string name="tint" msgid="154435943863418434">"Árnyalás"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignetta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Melegítés"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Rajzoljon a fotóra embléma létrehozásához"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Húzza a fotót annak tükrözéséhez"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Érintse meg a vörös szem hatás eltávolításához"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Húzza a fényképet annak forgatásához"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Húzza a fényképet annak kiegyenesítéséhez"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-in/strings.xml b/samples/PhotoEditor/res/values-in/strings.xml
new file mode 100644
index 0000000..7f3345c
--- /dev/null
+++ b/samples/PhotoEditor/res/values-in/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Tidak dapat memuat foto untuk diedit"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Terjadi masalah saat menyimpan"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> tersimpan"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Simpan foto yang diedit?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ya"</string>
+    <string name="no" msgid="5595408018304861875">"Tidak"</string>
+    <string name="cancel" msgid="6286688759430767307">"Batal"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Hasil edit"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistik"</string>
+    <string name="color_group" msgid="3390757893475444688">"Warna"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Pencahayaan"</string>
+    <string name="fix_group" msgid="971450155810247383">"Diperbaiki"</string>
+    <string name="autofix" msgid="1223859191856172755">"Lagi Beruntung"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proses silang"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumenter"</string>
+    <string name="doodle" msgid="1686409894518940990">"Orat-oret"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duo-warna"</string>
+    <string name="filllight" msgid="2644989991700022526">"Isi Cahaya"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Mata Ikan"</string>
+    <string name="flip" msgid="2357692401826287480">"Balik"</string>
+    <string name="grain" msgid="7487585304579789098">"Butiran Film"</string>
+    <string name="grayscale" msgid="615169770671286699">"Gradasi kelabu"</string>
+    <string name="highlight" msgid="3902653944386623972">"Sorotan"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Gaya Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterisasi"</string>
+    <string name="redeye" msgid="4958448806369928239">"Mata Merah"</string>
+    <string name="rotate" msgid="6607597269792373083">"Putar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturasi"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Bayangan"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Pertajam"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Fokus Lembut"</string>
+    <string name="straighten" msgid="5217801513491493491">"Luruskan"</string>
+    <string name="temperature" msgid="3589958696423284897">"Suhu"</string>
+    <string name="tint" msgid="154435943863418434">"Warna"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinyet"</string>
+    <string name="warmify" msgid="2404474974251014984">"Perhangat"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Menggambar pada foto untuk membuat orat-oret"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Seret foto untuk membaliknya"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Ketuk untuk menghapus mata merah"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Seret foto untuk memutarnya"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Seret foto untuk meluruskannya"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-it/strings.xml b/samples/PhotoEditor/res/values-it/strings.xml
new file mode 100644
index 0000000..e60d281
--- /dev/null
+++ b/samples/PhotoEditor/res/values-it/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Impossibile caricare la foto da modificare"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Si è verificato un problema durante il salvataggio"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> salvato"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Salvare la foto modificata?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sì"</string>
+    <string name="no" msgid="5595408018304861875">"No"</string>
+    <string name="cancel" msgid="6286688759430767307">"Annulla"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Modificata"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistici"</string>
+    <string name="color_group" msgid="3390757893475444688">"Colore"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Esposizione"</string>
+    <string name="fix_group" msgid="971450155810247383">"Correggi"</string>
+    <string name="autofix" msgid="1223859191856172755">"Mi sento fortunato"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Cross process"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentario"</string>
+    <string name="doodle" msgid="1686409894518940990">"Scarabocchio"</string>
+    <string name="duotone" msgid="8145893940788467106">"Bitonale"</string>
+    <string name="filllight" msgid="2644989991700022526">"Luce di riempimento"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Capovolgi"</string>
+    <string name="grain" msgid="7487585304579789098">"Grana pellicola"</string>
+    <string name="grayscale" msgid="615169770671286699">"Scala di grigi"</string>
+    <string name="highlight" msgid="3902653944386623972">"Luci"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Effetto Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizza"</string>
+    <string name="redeye" msgid="4958448806369928239">"Occhi rossi"</string>
+    <string name="rotate" msgid="6607597269792373083">"Ruota"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturazione"</string>
+    <string name="sepia" msgid="7978093531824705601">"Seppia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Ombre"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Nitidezza"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Sfocatura diffusa"</string>
+    <string name="straighten" msgid="5217801513491493491">"Raddrizza"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tinta"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignetta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Colori caldi"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Disegna sulla foto per scarabocchiarla"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Trascina la foto per girarla"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tocca per rimuovere gli occhi rossi"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Trascina la foto per ruotarla"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Trascina la foto per raddrizzarla"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-iw/strings.xml b/samples/PhotoEditor/res/values-iw/strings.xml
new file mode 100644
index 0000000..ea6a0fe
--- /dev/null
+++ b/samples/PhotoEditor/res/values-iw/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"לא ניתן לטעון את התמונה לעריכה"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"אירעה בעיה במהלך השמירה"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> נשמר"</string>
+    <string name="save_photo" msgid="3125109368779997862">"לשמור את התמונה הערוכה?"</string>
+    <string name="yes" msgid="5402582493291792293">"כן"</string>
+    <string name="no" msgid="5595408018304861875">"לא"</string>
+    <string name="cancel" msgid="6286688759430767307">"ביטול"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"תמונות ערוכות"</string>
+    <string name="artistic_group" msgid="573091775825402562">"אמנותי"</string>
+    <string name="color_group" msgid="3390757893475444688">"צבע"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"חשיפה"</string>
+    <string name="fix_group" msgid="971450155810247383">"תיקונים"</string>
+    <string name="autofix" msgid="1223859191856172755">"יותר מזל משכל"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"פיתוח מוצלב"</string>
+    <string name="documentary" msgid="50396326708699797">"דוקומנטרי"</string>
+    <string name="doodle" msgid="1686409894518940990">"ציור"</string>
+    <string name="duotone" msgid="8145893940788467106">"שני גוונים"</string>
+    <string name="filllight" msgid="2644989991700022526">"אור מילוי"</string>
+    <string name="fisheye" msgid="6037488646928998921">"עדשת עין הדג"</string>
+    <string name="flip" msgid="2357692401826287480">"הפוך"</string>
+    <string name="grain" msgid="7487585304579789098">"גרעיניות סרט"</string>
+    <string name="grayscale" msgid="615169770671286699">"גווני אפור"</string>
+    <string name="highlight" msgid="3902653944386623972">"הבהרה"</string>
+    <string name="lomoish" msgid="7793824845892532976">"בסגנון Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"נגטיב"</string>
+    <string name="posterize" msgid="4139212359561383385">"מצב פוסטר (Posterize)"</string>
+    <string name="redeye" msgid="4958448806369928239">"עין אדומה"</string>
+    <string name="rotate" msgid="6607597269792373083">"סיבוב"</string>
+    <string name="saturation" msgid="8621322012271169931">"רוויה"</string>
+    <string name="sepia" msgid="7978093531824705601">"חום-ספיה"</string>
+    <string name="shadow" msgid="8235188588101973090">"צלליות"</string>
+    <string name="sharpen" msgid="8449662378104403230">"שפר חדות"</string>
+    <string name="softfocus" msgid="6334228862566408303">"מיקוד רך"</string>
+    <string name="straighten" msgid="5217801513491493491">"יישר"</string>
+    <string name="temperature" msgid="3589958696423284897">"טמפרטורה"</string>
+    <string name="tint" msgid="154435943863418434">"גוון"</string>
+    <string name="vignette" msgid="7648125924662648282">"שוליים דהויים"</string>
+    <string name="warmify" msgid="2404474974251014984">"הפוך לחמים יותר"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"גרור על גבי התמונה כדי לצייר עליה"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"גרור את התמונה כדי להפוך אותה"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"הקש כדי להסיר עיניים אדומות"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"גרור את התמונה כדי לסובב אותה"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"גרור את התמונה כדי ליישר אותה"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-ja/strings.xml b/samples/PhotoEditor/res/values-ja/strings.xml
new file mode 100644
index 0000000..fcf47cc
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ja/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"フォトスタジオ"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"写真を編集用に読み込むことができません。"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"保存中に問題が発生しました"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g>を保存しました"</string>
+    <string name="save_photo" msgid="3125109368779997862">"編集した写真を保存しますか?"</string>
+    <string name="yes" msgid="5402582493291792293">"はい"</string>
+    <string name="no" msgid="5595408018304861875">"いいえ"</string>
+    <string name="cancel" msgid="6286688759430767307">"キャンセル"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"編集済み"</string>
+    <string name="artistic_group" msgid="573091775825402562">"芸術的"</string>
+    <string name="color_group" msgid="3390757893475444688">"色"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"露出"</string>
+    <string name="fix_group" msgid="971450155810247383">"修正"</string>
+    <string name="autofix" msgid="1223859191856172755">"ラッキー"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"クロスプロセス"</string>
+    <string name="documentary" msgid="50396326708699797">"ドキュメンタリー"</string>
+    <string name="doodle" msgid="1686409894518940990">"落書き"</string>
+    <string name="duotone" msgid="8145893940788467106">"デュオトーン"</string>
+    <string name="filllight" msgid="2644989991700022526">"明るさ調整"</string>
+    <string name="fisheye" msgid="6037488646928998921">"魚眼レンズ"</string>
+    <string name="flip" msgid="2357692401826287480">"反転"</string>
+    <string name="grain" msgid="7487585304579789098">"フィルムグレイン"</string>
+    <string name="grayscale" msgid="615169770671286699">"グレースケール"</string>
+    <string name="highlight" msgid="3902653944386623972">"ハイライト"</string>
+    <string name="lomoish" msgid="7793824845892532976">"LOMO風"</string>
+    <string name="negative" msgid="1985508917342811252">"白黒反転"</string>
+    <string name="posterize" msgid="4139212359561383385">"ポスタライズ"</string>
+    <string name="redeye" msgid="4958448806369928239">"赤目処理"</string>
+    <string name="rotate" msgid="6607597269792373083">"回転"</string>
+    <string name="saturation" msgid="8621322012271169931">"彩度"</string>
+    <string name="sepia" msgid="7978093531824705601">"セピア"</string>
+    <string name="shadow" msgid="8235188588101973090">"シャドウ"</string>
+    <string name="sharpen" msgid="8449662378104403230">"シャープ"</string>
+    <string name="softfocus" msgid="6334228862566408303">"ソフトフォーカス"</string>
+    <string name="straighten" msgid="5217801513491493491">"傾き調整"</string>
+    <string name="temperature" msgid="3589958696423284897">"温度"</string>
+    <string name="tint" msgid="154435943863418434">"色合い"</string>
+    <string name="vignette" msgid="7648125924662648282">"周辺減光"</string>
+    <string name="warmify" msgid="2404474974251014984">"ウォーム"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"画像の上に書き込みます"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"画像をめくるにはドラッグします"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"赤目を補正するにはタップします"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"画像を回転させるにはドラッグします"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"画像をまっすぐにするにはドラッグします"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-ko/strings.xml b/samples/PhotoEditor/res/values-ko/strings.xml
new file mode 100644
index 0000000..2eaa100
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ko/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"수정할 사진을 로드할 수 없습니다."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"저장하는 동안 문제가 발생했습니다."</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g>이(가) 저장되었습니다."</string>
+    <string name="save_photo" msgid="3125109368779997862">"수정한 사진을 저장하시겠습니까?"</string>
+    <string name="yes" msgid="5402582493291792293">"예"</string>
+    <string name="no" msgid="5595408018304861875">"아니요"</string>
+    <string name="cancel" msgid="6286688759430767307">"취소"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"수정된 사진"</string>
+    <string name="artistic_group" msgid="573091775825402562">"예술적"</string>
+    <string name="color_group" msgid="3390757893475444688">"색상"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"노출"</string>
+    <string name="fix_group" msgid="971450155810247383">"조절"</string>
+    <string name="autofix" msgid="1223859191856172755">"Feel Lucky"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"크로스 프로세스"</string>
+    <string name="documentary" msgid="50396326708699797">"다큐멘터리"</string>
+    <string name="doodle" msgid="1686409894518940990">"낙서"</string>
+    <string name="duotone" msgid="8145893940788467106">"더블톤"</string>
+    <string name="filllight" msgid="2644989991700022526">"밝게"</string>
+    <string name="fisheye" msgid="6037488646928998921">"어안렌즈"</string>
+    <string name="flip" msgid="2357692401826287480">"방향 바꾸기"</string>
+    <string name="grain" msgid="7487585304579789098">"거친 필름"</string>
+    <string name="grayscale" msgid="615169770671286699">"그레이 스케일"</string>
+    <string name="highlight" msgid="3902653944386623972">"하이라이트"</string>
+    <string name="lomoish" msgid="7793824845892532976">"로모 스타일"</string>
+    <string name="negative" msgid="1985508917342811252">"네거티브"</string>
+    <string name="posterize" msgid="4139212359561383385">"포스터"</string>
+    <string name="redeye" msgid="4958448806369928239">"적목현상"</string>
+    <string name="rotate" msgid="6607597269792373083">"회전"</string>
+    <string name="saturation" msgid="8621322012271169931">"채도"</string>
+    <string name="sepia" msgid="7978093531824705601">"세피아"</string>
+    <string name="shadow" msgid="8235188588101973090">"그림자"</string>
+    <string name="sharpen" msgid="8449662378104403230">"선명하게"</string>
+    <string name="softfocus" msgid="6334228862566408303">"소프트 포커스"</string>
+    <string name="straighten" msgid="5217801513491493491">"똑바르게"</string>
+    <string name="temperature" msgid="3589958696423284897">"색온도"</string>
+    <string name="tint" msgid="154435943863418434">"농담"</string>
+    <string name="vignette" msgid="7648125924662648282">"비네트"</string>
+    <string name="warmify" msgid="2404474974251014984">"따뜻하게"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"사진에 그림을 그려 낙서하기"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"사진을 드래그하여 방향 바꾸기"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"터치하여 적목 현상 제거"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"드래그하여 사진 회전"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"사진을 드래그하여 반듯하게 하기"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-lt/strings.xml b/samples/PhotoEditor/res/values-lt/strings.xml
new file mode 100644
index 0000000..f49fe84
--- /dev/null
+++ b/samples/PhotoEditor/res/values-lt/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Neįmanoma įkelti nuotr., kad ją būtų g. redaguoti."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Išsaugant iškilo problema"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> išsaugota"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Išsaugoti redaguotą nuotrauką?"</string>
+    <string name="yes" msgid="5402582493291792293">"Taip"</string>
+    <string name="no" msgid="5595408018304861875">"Ne"</string>
+    <string name="cancel" msgid="6286688759430767307">"Atšaukti"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redaguota"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Meniška"</string>
+    <string name="color_group" msgid="3390757893475444688">"Spalva"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Išlaikymas"</string>
+    <string name="fix_group" msgid="971450155810247383">"Pataisyti"</string>
+    <string name="autofix" msgid="1223859191856172755">"Man sekasi"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Kryžm. apdor."</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentika"</string>
+    <string name="doodle" msgid="1686409894518940990">"Logotipų puoš."</string>
+    <string name="duotone" msgid="8145893940788467106">"Du tonai"</string>
+    <string name="filllight" msgid="2644989991700022526">"Užpild. šviesa"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Žuvies akis"</string>
+    <string name="flip" msgid="2357692401826287480">"Apversti"</string>
+    <string name="grain" msgid="7487585304579789098">"Juostos grūd."</string>
+    <string name="grayscale" msgid="615169770671286699">"Pilkumo tonas"</string>
+    <string name="highlight" msgid="3902653944386623972">"Paryškinimai"</string>
+    <string name="lomoish" msgid="7793824845892532976">"„Lomo“ tipo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatyvas"</string>
+    <string name="posterize" msgid="4139212359561383385">"Mažinti spalv."</string>
+    <string name="redeye" msgid="4958448806369928239">"Raudonos akys"</string>
+    <string name="rotate" msgid="6607597269792373083">"Pasukti"</string>
+    <string name="saturation" msgid="8621322012271169931">"Spalvų sodr."</string>
+    <string name="sepia" msgid="7978093531824705601">"Tamsiai rusv."</string>
+    <string name="shadow" msgid="8235188588101973090">"Šešėliai"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Paryškinti"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Švelnus fokus."</string>
+    <string name="straighten" msgid="5217801513491493491">"Tiesinti"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatūra"</string>
+    <string name="tint" msgid="154435943863418434">"Atspalvis"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinjetė"</string>
+    <string name="warmify" msgid="2404474974251014984">"Naud. šilt. sp."</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Pieškite ant nuotraukos, jei norite taikyti logotipų puošybą"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Vilkite nuotrauką, kad ją apverstumėte"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Palieskite, kad pašalintumėte raudonas akis"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Vilkite nuotrauką, kad ją apsuktumėte"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Vilkite nuotrauką, kad ją ištiesintumėte"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-lv/strings.xml b/samples/PhotoEditor/res/values-lv/strings.xml
new file mode 100644
index 0000000..465d12b
--- /dev/null
+++ b/samples/PhotoEditor/res/values-lv/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Fotostudija"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Nevar ielādēt fotoattēlu, lai to rediģētu."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Saglabāšanas laikā radās problēma."</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Fails <xliff:g id="FILENAME">%s</xliff:g> ir saglabāts."</string>
+    <string name="save_photo" msgid="3125109368779997862">"Vai saglabāt rediģēto fotoattēlu?"</string>
+    <string name="yes" msgid="5402582493291792293">"Jā"</string>
+    <string name="no" msgid="5595408018304861875">"Nē"</string>
+    <string name="cancel" msgid="6286688759430767307">"Atcelt"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Rediģēti"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Māksla"</string>
+    <string name="color_group" msgid="3390757893475444688">"Krāsa"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Ekspozīcija"</string>
+    <string name="fix_group" msgid="971450155810247383">"Labot"</string>
+    <string name="autofix" msgid="1223859191856172755">"Ticu veiksmei"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Kļūd. apstrāde"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentārs"</string>
+    <string name="doodle" msgid="1686409894518940990">"Kricelējums"</string>
+    <string name="duotone" msgid="8145893940788467106">"Divtoņu"</string>
+    <string name="filllight" msgid="2644989991700022526">"Izm. pretgaismu"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Platleņķis"</string>
+    <string name="flip" msgid="2357692401826287480">"Apvērst"</string>
+    <string name="grain" msgid="7487585304579789098">"Graudaina filma"</string>
+    <string name="grayscale" msgid="615169770671286699">"Pelēktoņu"</string>
+    <string name="highlight" msgid="3902653944386623972">"Gaismas efekti"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomogrāfija"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatīvs"</string>
+    <string name="posterize" msgid="4139212359561383385">"Veidot plakātu"</string>
+    <string name="redeye" msgid="4958448806369928239">"Sarkanās acis"</string>
+    <string name="rotate" msgid="6607597269792373083">"Pagriezt"</string>
+    <string name="saturation" msgid="8621322012271169931">"Piesātinājums"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sēpija"</string>
+    <string name="shadow" msgid="8235188588101973090">"Ēnas"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Padarīt asāku"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Neass attēls"</string>
+    <string name="straighten" msgid="5217801513491493491">"Iztaisnot"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatūra"</string>
+    <string name="tint" msgid="154435943863418434">"Nianse"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinjete"</string>
+    <string name="warmify" msgid="2404474974251014984">"Siltināt"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Velciet uz fotoattēla, lai kricelētu."</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Velciet fotoattēlu, lai to apgrieztu."</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Pieskarieties, lai noņemtu sarkano acu efektu."</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Velciet fotoattēlu, lai to pagrieztu."</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Velciet fotoattēlu, lai to iztaisnotu."</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-ms/strings.xml b/samples/PhotoEditor/res/values-ms/strings.xml
new file mode 100644
index 0000000..f6c2e62
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ms/strings.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Tidak boleh memuatkan foto untuk pengeditan"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Ralat berlaku semasa menyimpan"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> disimpan"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Simpan foto yang diedit?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ya"</string>
+    <string name="no" msgid="5595408018304861875">"Tidak"</string>
+    <string name="cancel" msgid="6286688759430767307">"Batal"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Diedit"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistik"</string>
+    <string name="color_group" msgid="3390757893475444688">"Warna"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Dedahan"</string>
+    <string name="fix_group" msgid="971450155810247383">"Baiki"</string>
+    <string name="autofix" msgid="1223859191856172755">"Rasa Bertuah"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proses silang"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentari"</string>
+    <string name="doodle" msgid="1686409894518940990">"Coretan"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dua tona"</string>
+    <string name="filllight" msgid="2644989991700022526">"Cahaya Pemenuh"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Mata ikan"</string>
+    <string name="flip" msgid="2357692401826287480">"Balikkan"</string>
+    <string name="grain" msgid="7487585304579789098">"Ira Filem"</string>
+    <string name="grayscale" msgid="615169770671286699">"Skala kelabu"</string>
+    <string name="highlight" msgid="3902653944386623972">"Serlahan"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Seperti Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
+    <string name="posterize" msgid="4139212359561383385">"Pemposteran"</string>
+    <string name="redeye" msgid="4958448806369928239">"Mata Merah"</string>
+    <string name="rotate" msgid="6607597269792373083">"Putar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Ketepuan"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Bayang-bayang"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Tajamkan"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Fokus Lembut"</string>
+    <string name="straighten" msgid="5217801513491493491">"Luruskan"</string>
+    <string name="temperature" msgid="3589958696423284897">"Suhu"</string>
+    <string name="tint" msgid="154435943863418434">"Seri warna"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignet"</string>
+    <string name="warmify" msgid="2404474974251014984">"Hangatkan"</string>
+    <!-- no translation found for doodle_tooltip (2902117272374362915) -->
+    <skip />
+    <!-- no translation found for flip_tooltip (2700943256714731737) -->
+    <skip />
+    <!-- no translation found for redeye_tooltip (9112774042113471358) -->
+    <skip />
+    <!-- no translation found for rotate_tooltip (7008602969130734229) -->
+    <skip />
+    <!-- no translation found for straighten_tooltip (4846317027139212339) -->
+    <skip />
+</resources>
diff --git a/samples/PhotoEditor/res/values-nb/strings.xml b/samples/PhotoEditor/res/values-nb/strings.xml
new file mode 100644
index 0000000..b36ca86
--- /dev/null
+++ b/samples/PhotoEditor/res/values-nb/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Kan ikke laste inn bildet for redigering"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Det oppstod et problem under lagring"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> er lagret"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Vil du lagre det redigerte bildet?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ja"</string>
+    <string name="no" msgid="5595408018304861875">"Nei"</string>
+    <string name="cancel" msgid="6286688759430767307">"Avbryt"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigert"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Kunstnerisk"</string>
+    <string name="color_group" msgid="3390757893475444688">"Farge"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Eksponering"</string>
+    <string name="fix_group" msgid="971450155810247383">"Fiks"</string>
+    <string name="autofix" msgid="1223859191856172755">"Ta sjansen"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Kryssfremkall."</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentar"</string>
+    <string name="doodle" msgid="1686409894518940990">"Drodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Tofargebilde"</string>
+    <string name="filllight" msgid="2644989991700022526">"Utfyllingslys"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fiskeøye"</string>
+    <string name="flip" msgid="2357692401826287480">"Vend"</string>
+    <string name="grain" msgid="7487585304579789098">"Kornete oppløs."</string>
+    <string name="grayscale" msgid="615169770671286699">"Gråtoner"</string>
+    <string name="highlight" msgid="3902653944386623972">"Gjør lysere"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-aktig"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativt"</string>
+    <string name="posterize" msgid="4139212359561383385">"Plakateffekt"</string>
+    <string name="redeye" msgid="4958448806369928239">"Røde øyne"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotering"</string>
+    <string name="saturation" msgid="8621322012271169931">"Metning"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Skygger"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Gjør skarpere"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Kantdiffusering"</string>
+    <string name="straighten" msgid="5217801513491493491">"Rett opp"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatur"</string>
+    <string name="tint" msgid="154435943863418434">"Fargetone"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignettering"</string>
+    <string name="warmify" msgid="2404474974251014984">"Gjør varmere"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på bildet for å drodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Dra bildet for å snu det"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Trykk for å fjerne røde øyne"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Dra bildet for å rotere det"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Dra bilde for å rette på det"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-nl/strings.xml b/samples/PhotoEditor/res/values-nl/strings.xml
new file mode 100644
index 0000000..9c7e946
--- /dev/null
+++ b/samples/PhotoEditor/res/values-nl/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Kan de foto niet laden om te bewerken"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Er is een probleem opgetreden tijdens het opslaan"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> opgeslagen"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Bewerkte foto opslaan?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ja"</string>
+    <string name="no" msgid="5595408018304861875">"Nee"</string>
+    <string name="cancel" msgid="6286688759430767307">"Annuleren"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Bewerkt"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistiek"</string>
+    <string name="color_group" msgid="3390757893475444688">"Kleur"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Belichting"</string>
+    <string name="fix_group" msgid="971450155810247383">"Verbeteren"</string>
+    <string name="autofix" msgid="1223859191856172755">"Ik doe een gok"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Kruisverwerking"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentaire"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duotone"</string>
+    <string name="filllight" msgid="2644989991700022526">"Licht invullen"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Visooglens"</string>
+    <string name="flip" msgid="2357692401826287480">"Spiegelen"</string>
+    <string name="grain" msgid="7487585304579789098">"Korrelstructuur"</string>
+    <string name="grayscale" msgid="615169770671286699">"Grijstinten"</string>
+    <string name="highlight" msgid="3902653944386623972">"Lichter maken"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-achtig"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatief"</string>
+    <string name="posterize" msgid="4139212359561383385">"Postereffect"</string>
+    <string name="redeye" msgid="4958448806369928239">"Rode ogen"</string>
+    <string name="rotate" msgid="6607597269792373083">"Draaien"</string>
+    <string name="saturation" msgid="8621322012271169931">"Verzadiging"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Schaduwen"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Scherper maken"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Zachte focus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Recht maken"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatuur"</string>
+    <string name="tint" msgid="154435943863418434">"Tint"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vervloeiende randen"</string>
+    <string name="warmify" msgid="2404474974251014984">"Warmer maken"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Teken op de foto om hierop te krabbelen"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Sleep de foto om deze te spiegelen"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tik om rode ogen te verwijderen"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Sleep de foto om deze te draaien"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Sleep de foto om deze recht te zetten"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-pl/strings.xml b/samples/PhotoEditor/res/values-pl/strings.xml
new file mode 100644
index 0000000..c14104c
--- /dev/null
+++ b/samples/PhotoEditor/res/values-pl/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Studio fotograficzne"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Nie można wczytać zdjęcia do edytowania."</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Podczas zapisu wystąpił problem."</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Plik <xliff:g id="FILENAME">%s</xliff:g> został zapisany."</string>
+    <string name="save_photo" msgid="3125109368779997862">"Czy zapisać edytowane zdjęcie?"</string>
+    <string name="yes" msgid="5402582493291792293">"Tak"</string>
+    <string name="no" msgid="5595408018304861875">"Nie"</string>
+    <string name="cancel" msgid="6286688759430767307">"Anuluj"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Edytowane"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artystyczne"</string>
+    <string name="color_group" msgid="3390757893475444688">"Kolor"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Ekspozycja"</string>
+    <string name="fix_group" msgid="971450155810247383">"Poprawki"</string>
+    <string name="autofix" msgid="1223859191856172755">"Szczęśliwy traf"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proces krosowy"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
+    <string name="doodle" msgid="1686409894518940990">"Bazgroły"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dwuton"</string>
+    <string name="filllight" msgid="2644989991700022526">"Doświetlenie"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Rybie oko"</string>
+    <string name="flip" msgid="2357692401826287480">"Odwrócenie"</string>
+    <string name="grain" msgid="7487585304579789098">"Ziarno kliszy"</string>
+    <string name="grayscale" msgid="615169770671286699">"Skala szarości"</string>
+    <string name="highlight" msgid="3902653944386623972">"Podświetlenie"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Łomografia"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatyw"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posteryzacja"</string>
+    <string name="redeye" msgid="4958448806369928239">"Czerwone oczy"</string>
+    <string name="rotate" msgid="6607597269792373083">"Obrót"</string>
+    <string name="saturation" msgid="8621322012271169931">"Nasycenie"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Cienie"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Wyostrzenie"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Nieostrość"</string>
+    <string name="straighten" msgid="5217801513491493491">"Wyprostowanie"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Odcień"</string>
+    <string name="vignette" msgid="7648125924662648282">"Winietowanie"</string>
+    <string name="warmify" msgid="2404474974251014984">"Ocieplenie"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Rysuj po zdjęciu, aby dodać bazgroły."</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Przeciągnij zdjęcie, aby je odwrócić."</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Dotknij, aby usunąć efekt czerwonych oczu."</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Przeciągnij zdjęcie, aby je obrócić."</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Przeciągnij zdjęcie, aby je wyprostować."</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-pt-rPT/strings.xml b/samples/PhotoEditor/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..9547595
--- /dev/null
+++ b/samples/PhotoEditor/res/values-pt-rPT/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Não é possível carregar a fotografia para edição"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Ocorreu um problema ao guardar"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> guardado"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Guardar a fotografia editada?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sim"</string>
+    <string name="no" msgid="5595408018304861875">"Não"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancelar"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editado"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artístico"</string>
+    <string name="color_group" msgid="3390757893475444688">"Cor"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposição"</string>
+    <string name="fix_group" msgid="971450155810247383">"Corrigir"</string>
+    <string name="autofix" msgid="1223859191856172755">"Sente-se com Sorte"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Processo cruzado"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentário"</string>
+    <string name="doodle" msgid="1686409894518940990">"Rabiscar"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dois tons"</string>
+    <string name="filllight" msgid="2644989991700022526">"Preench. Claro"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Olho de peixe"</string>
+    <string name="flip" msgid="2357692401826287480">"Inverter"</string>
+    <string name="grain" msgid="7487585304579789098">"Filme Granulado"</string>
+    <string name="grayscale" msgid="615169770671286699">"Escala de cinzentos"</string>
+    <string name="highlight" msgid="3902653944386623972">"Realces"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Pseudo Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
+    <string name="posterize" msgid="4139212359561383385">"Póster"</string>
+    <string name="redeye" msgid="4958448806369928239">"Olhos Vermelhos"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rodar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturação"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Aumentar nitidez"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Focagem suave"</string>
+    <string name="straighten" msgid="5217801513491493491">"Endireitar"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tonalidade"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinheta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Tons quentes"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na fotografia para rabiscar"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Arraste a fotografia para virá-la"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Toque para remover o efeito de olhos vermelhos"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a fotografia para rodá-la"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a fotografia para endireitá-la"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-pt/strings.xml b/samples/PhotoEditor/res/values-pt/strings.xml
new file mode 100644
index 0000000..132fb68
--- /dev/null
+++ b/samples/PhotoEditor/res/values-pt/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Não é possível carregar a foto para edição"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Ocorreu um problema durante a ação de salvar"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> salvo"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Salvar foto editada?"</string>
+    <string name="yes" msgid="5402582493291792293">"Sim"</string>
+    <string name="no" msgid="5595408018304861875">"Não"</string>
+    <string name="cancel" msgid="6286688759430767307">"Cancelar"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editado"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artístico"</string>
+    <string name="color_group" msgid="3390757893475444688">"Cor"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposição"</string>
+    <string name="fix_group" msgid="971450155810247383">"Fixar"</string>
+    <string name="autofix" msgid="1223859191856172755">"Se sente sort."</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Proc. cruzado"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentário"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duotone"</string>
+    <string name="filllight" msgid="2644989991700022526">"Luz de preench."</string>
+    <string name="fisheye" msgid="6037488646928998921">"Olho de peixe"</string>
+    <string name="flip" msgid="2357692401826287480">"Inverter"</string>
+    <string name="grain" msgid="7487585304579789098">"Granulado"</string>
+    <string name="grayscale" msgid="615169770671286699">"Escala de cinza"</string>
+    <string name="highlight" msgid="3902653944386623972">"Destaques"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Est. câm. Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
+    <string name="posterize" msgid="4139212359561383385">"Transf. em pôster"</string>
+    <string name="redeye" msgid="4958448806369928239">"Olhos vermelhos"</string>
+    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturação"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Aument. nitidez"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Filtro difusor"</string>
+    <string name="straighten" msgid="5217801513491493491">"Endireitar"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tingir"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinheta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Tons + quentes"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na foto para rabiscar"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Arraste a foto para invertê-la"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Toque para remover olhos vermelhos"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a foto para girá-la"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a foto para endireitá-la"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-ro/strings.xml b/samples/PhotoEditor/res/values-ro/strings.xml
new file mode 100644
index 0000000..997e41c
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ro/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Studio foto"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Nu se poate încărca fotografia pentru editare"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"A apărut o problemă la salvare"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> salvate"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Salvaţi fotografia editată?"</string>
+    <string name="yes" msgid="5402582493291792293">"Da"</string>
+    <string name="no" msgid="5595408018304861875">"Nu"</string>
+    <string name="cancel" msgid="6286688759430767307">"Anulaţi"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editate"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistic"</string>
+    <string name="color_group" msgid="3390757893475444688">"Culoare"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Expunere"</string>
+    <string name="fix_group" msgid="971450155810247383">"Remediere"</string>
+    <string name="autofix" msgid="1223859191856172755">"Mă simt norocos"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Suprapunere"</string>
+    <string name="documentary" msgid="50396326708699797">"Documentar"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"În două culori"</string>
+    <string name="filllight" msgid="2644989991700022526">"Lumină complet."</string>
+    <string name="fisheye" msgid="6037488646928998921">"Ochi de peşte"</string>
+    <string name="flip" msgid="2357692401826287480">"Răsturnare"</string>
+    <string name="grain" msgid="7487585304579789098">"Granulaţie film"</string>
+    <string name="grayscale" msgid="615169770671286699">"Tonuri de gri"</string>
+    <string name="highlight" msgid="3902653944386623972">"Evidenţieri"</string>
+    <string name="lomoish" msgid="7793824845892532976">"În stilul Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Poster"</string>
+    <string name="redeye" msgid="4958448806369928239">"Ochi roşii"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotaţie"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturaţie"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Umbre"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Clarificare"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Defocalizare"</string>
+    <string name="straighten" msgid="5217801513491493491">"Îndreptare"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatură"</string>
+    <string name="tint" msgid="154435943863418434">"Haşură"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignetare"</string>
+    <string name="warmify" msgid="2404474974251014984">"Tonuri mai calde"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenaţi pe fotografie pentru a aplica efectul doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Trageţi o fotografie pentru a o răsturna"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Apăsaţi pentru a elimina ochii roşii"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Trageţi fotografia pentru a o roti"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Trageţi de fotografie pentru a o îndrepta"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-ru/strings.xml b/samples/PhotoEditor/res/values-ru/strings.xml
new file mode 100644
index 0000000..f8f19e9
--- /dev/null
+++ b/samples/PhotoEditor/res/values-ru/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Фотостудия"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Не удается загрузить фото для изменения"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"При сохранении произошла ошибка"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Фото <xliff:g id="FILENAME">%s</xliff:g> сохранено"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Сохранить изменения?"</string>
+    <string name="yes" msgid="5402582493291792293">"Да"</string>
+    <string name="no" msgid="5595408018304861875">"Нет"</string>
+    <string name="cancel" msgid="6286688759430767307">"Отмена"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Измененные"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Художник"</string>
+    <string name="color_group" msgid="3390757893475444688">"Цвет"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Экспозиция"</string>
+    <string name="fix_group" msgid="971450155810247383">"Исправление"</string>
+    <string name="autofix" msgid="1223859191856172755">"Мне повезет!"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Кроссобработка"</string>
+    <string name="documentary" msgid="50396326708699797">"Документальные"</string>
+    <string name="doodle" msgid="1686409894518940990">"Подмалевок"</string>
+    <string name="duotone" msgid="8145893940788467106">"Двойной тон"</string>
+    <string name="filllight" msgid="2644989991700022526">"Осветление"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Рыбий глаз"</string>
+    <string name="flip" msgid="2357692401826287480">"Отразить"</string>
+    <string name="grain" msgid="7487585304579789098">"Зернистость"</string>
+    <string name="grayscale" msgid="615169770671286699">"Оттенки серого"</string>
+    <string name="highlight" msgid="3902653944386623972">"Блики"</string>
+    <string name="lomoish" msgid="7793824845892532976">"В стиле Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
+    <string name="posterize" msgid="4139212359561383385">"Постеризация"</string>
+    <string name="redeye" msgid="4958448806369928239">"Красные глаза"</string>
+    <string name="rotate" msgid="6607597269792373083">"Поворот"</string>
+    <string name="saturation" msgid="8621322012271169931">"Насыщенность"</string>
+    <string name="sepia" msgid="7978093531824705601">"Сепия"</string>
+    <string name="shadow" msgid="8235188588101973090">"Затенение"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Резкость"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Мягкий фокус"</string>
+    <string name="straighten" msgid="5217801513491493491">"Выравнивание"</string>
+    <string name="temperature" msgid="3589958696423284897">"Температура"</string>
+    <string name="tint" msgid="154435943863418434">"Оттенок"</string>
+    <string name="vignette" msgid="7648125924662648282">"Виньетка"</string>
+    <string name="warmify" msgid="2404474974251014984">"Теплые тона"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Рисуйте на фото пальцем"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Чтобы отразить фото, перетащите его край"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Нажмите для устранения эффекта красных глаз"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Чтобы повернуть фото, перетащите его край"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Чтобы выровнять фото, перетащите его"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-sk/strings.xml b/samples/PhotoEditor/res/values-sk/strings.xml
new file mode 100644
index 0000000..c6379d0
--- /dev/null
+++ b/samples/PhotoEditor/res/values-sk/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Fotografiu nie je možné načítať na úpravy"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Pri ukladaní sa vyskytol problém"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Fotografie <xliff:g id="FILENAME">%s</xliff:g> boli uložené"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Chcete upravenú fotografiu uložiť?"</string>
+    <string name="yes" msgid="5402582493291792293">"Áno"</string>
+    <string name="no" msgid="5595408018304861875">"Nie"</string>
+    <string name="cancel" msgid="6286688759430767307">"Zrušiť"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Upravené"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Umelecké"</string>
+    <string name="color_group" msgid="3390757893475444688">"Farba"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Expozícia"</string>
+    <string name="fix_group" msgid="971450155810247383">"Korekcia"</string>
+    <string name="autofix" msgid="1223859191856172755">"Skúsim šťastie"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Prelínanie"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
+    <string name="doodle" msgid="1686409894518940990">"Kreslenie"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dvojtónový efekt"</string>
+    <string name="filllight" msgid="2644989991700022526">"Zad. osvetlenie"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Rybie oko"</string>
+    <string name="flip" msgid="2357692401826287480">"Prevrátenie"</string>
+    <string name="grain" msgid="7487585304579789098">"Zrnitý film"</string>
+    <string name="grayscale" msgid="615169770671286699">"Stupne sivej"</string>
+    <string name="highlight" msgid="3902653944386623972">"Prisvietenie"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Štýl Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatív"</string>
+    <string name="posterize" msgid="4139212359561383385">"Posterizácia"</string>
+    <string name="redeye" msgid="4958448806369928239">"Červené oči"</string>
+    <string name="rotate" msgid="6607597269792373083">"Otočenie"</string>
+    <string name="saturation" msgid="8621322012271169931">"Sýtosť"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sépiové farby"</string>
+    <string name="shadow" msgid="8235188588101973090">"Tiene"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Zaostrenie"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Jemné rozostr."</string>
+    <string name="straighten" msgid="5217801513491493491">"Vyrovnanie"</string>
+    <string name="temperature" msgid="3589958696423284897">"Teplota"</string>
+    <string name="tint" msgid="154435943863418434">"Tónovanie"</string>
+    <string name="vignette" msgid="7648125924662648282">"Medailónik"</string>
+    <string name="warmify" msgid="2404474974251014984">"Teplejšie farby"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii môžete kresliť prstom"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Pretiahnutím prstom fotografiu prevrátite"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Klepnutím odstráňte efekt červených očí"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Potiahnutím prstom fotografiu otočíte"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Potiahnutím prstom fotografiu vyrovnáte"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-sl/strings.xml b/samples/PhotoEditor/res/values-sl/strings.xml
new file mode 100644
index 0000000..1f2b906
--- /dev/null
+++ b/samples/PhotoEditor/res/values-sl/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Fotografije ni mogoče naložiti za urejanje"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Med shranjevanjem je prišlo do težave"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Datoteka <xliff:g id="FILENAME">%s</xliff:g> je shranjena"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Želite shraniti urejene fotografije?"</string>
+    <string name="yes" msgid="5402582493291792293">"Da"</string>
+    <string name="no" msgid="5595408018304861875">"Ne"</string>
+    <string name="cancel" msgid="6286688759430767307">"Prekliči"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Urejeno"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Umetniško"</string>
+    <string name="color_group" msgid="3390757893475444688">"Barva"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Osvetlitev"</string>
+    <string name="fix_group" msgid="971450155810247383">"Popravki"</string>
+    <string name="autofix" msgid="1223859191856172755">"Samodejno"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Navzkr. obdel."</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentarno"</string>
+    <string name="doodle" msgid="1686409894518940990">"Čačka"</string>
+    <string name="duotone" msgid="8145893940788467106">"Dvotonsko"</string>
+    <string name="filllight" msgid="2644989991700022526">"Dosvetlitev"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Ribje oko"</string>
+    <string name="flip" msgid="2357692401826287480">"Obrnjeno"</string>
+    <string name="grain" msgid="7487585304579789098">"Zrnatost filma"</string>
+    <string name="grayscale" msgid="615169770671286699">"Sivine"</string>
+    <string name="highlight" msgid="3902653944386623972">"Poudarki"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Slog lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Poster"</string>
+    <string name="redeye" msgid="4958448806369928239">"Rdeče oči"</string>
+    <string name="rotate" msgid="6607597269792373083">"Zasukaj"</string>
+    <string name="saturation" msgid="8621322012271169931">"Nasičenost"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepija"</string>
+    <string name="shadow" msgid="8235188588101973090">"Sence"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Izostri"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Megleno"</string>
+    <string name="straighten" msgid="5217801513491493491">"Poravnavanje"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Obarvanje"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinjeta"</string>
+    <string name="warmify" msgid="2404474974251014984">"Toplejši toni"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Povlecite po fotografiji za prostoročno risanje"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Povlecite fotografijo, da jo obrnete"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tapnite, da odstranite rdeče oči"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Povlecite fotografijo, da jo zavrtite"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Povlecite fotografijo, da jo poravnate"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-sr/strings.xml b/samples/PhotoEditor/res/values-sr/strings.xml
new file mode 100644
index 0000000..67cef0f
--- /dev/null
+++ b/samples/PhotoEditor/res/values-sr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Фото-студио"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Није могуће учитати фотографију за измену"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Дошло је до проблема приликом чувања"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Сачувана је датотека <xliff:g id="FILENAME">%s</xliff:g>"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Желите ли да сачувате измењену фотографију?"</string>
+    <string name="yes" msgid="5402582493291792293">"Да"</string>
+    <string name="no" msgid="5595408018304861875">"Не"</string>
+    <string name="cancel" msgid="6286688759430767307">"Откажи"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Измењено"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Уметнички"</string>
+    <string name="color_group" msgid="3390757893475444688">"Боја"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Експозиција"</string>
+    <string name="fix_group" msgid="971450155810247383">"Фиксирање"</string>
+    <string name="autofix" msgid="1223859191856172755">"Аутоматски"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Унакрсна обрада"</string>
+    <string name="documentary" msgid="50396326708699797">"Документарни"</string>
+    <string name="doodle" msgid="1686409894518940990">"Дудл логотип"</string>
+    <string name="duotone" msgid="8145893940788467106">"Дуо-тон"</string>
+    <string name="filllight" msgid="2644989991700022526">"Светлина"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Рибље око"</string>
+    <string name="flip" msgid="2357692401826287480">"Обрни"</string>
+    <string name="grain" msgid="7487585304579789098">"Зрнастост филма"</string>
+    <string name="grayscale" msgid="615169770671286699">"Црно-бело"</string>
+    <string name="highlight" msgid="3902653944386623972">"Истакнуте ставке"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Ломографски"</string>
+    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
+    <string name="posterize" msgid="4139212359561383385">"Постеризуј"</string>
+    <string name="redeye" msgid="4958448806369928239">"Црвене очи"</string>
+    <string name="rotate" msgid="6607597269792373083">"Ротирај"</string>
+    <string name="saturation" msgid="8621322012271169931">"Засићење"</string>
+    <string name="sepia" msgid="7978093531824705601">"Сепија"</string>
+    <string name="shadow" msgid="8235188588101973090">"Сенке"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Изоштри"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Меки фокус"</string>
+    <string name="straighten" msgid="5217801513491493491">"Исправи"</string>
+    <string name="temperature" msgid="3589958696423284897">"Температура"</string>
+    <string name="tint" msgid="154435943863418434">"Сенка"</string>
+    <string name="vignette" msgid="7648125924662648282">"Вињета"</string>
+    <string name="warmify" msgid="2404474974251014984">"Топлије боје"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Превуците слику на дудл логотип"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Превуците фотографију да бисте је окренули"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Додирните да бисте уклонили ефекат црвених очију"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Превуците фотографију да бисте је ротирали"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Превуците фотографију да бисте је исправили"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-sv/strings.xml b/samples/PhotoEditor/res/values-sv/strings.xml
new file mode 100644
index 0000000..07091f1
--- /dev/null
+++ b/samples/PhotoEditor/res/values-sv/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Fotostudio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Kan inte läsa in fotot för redigering"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Det gick inte att spara"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> sparades"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Spara redigerat foto?"</string>
+    <string name="yes" msgid="5402582493291792293">"Ja"</string>
+    <string name="no" msgid="5595408018304861875">"Nej"</string>
+    <string name="cancel" msgid="6286688759430767307">"Avbryt"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigerade"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Konstnärlig"</string>
+    <string name="color_group" msgid="3390757893475444688">"Färg"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exponering"</string>
+    <string name="fix_group" msgid="971450155810247383">"Korrigera"</string>
+    <string name="autofix" msgid="1223859191856172755">"Jag har tur"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Cross Process"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentär"</string>
+    <string name="doodle" msgid="1686409894518940990">"Klotter"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duoton"</string>
+    <string name="filllight" msgid="2644989991700022526">"Fyll belysning"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Vänd"</string>
+    <string name="grain" msgid="7487585304579789098">"Filmkornighet"</string>
+    <string name="grayscale" msgid="615169770671286699">"Gråskala"</string>
+    <string name="highlight" msgid="3902653944386623972">"Högdager"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-stil"</string>
+    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
+    <string name="posterize" msgid="4139212359561383385">"Färgreduktion"</string>
+    <string name="redeye" msgid="4958448806369928239">"Röda ögon"</string>
+    <string name="rotate" msgid="6607597269792373083">"Rotera"</string>
+    <string name="saturation" msgid="8621322012271169931">"Mättnad"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Skuggor"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Skarpare"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Minska fokus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Räta ut"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatur"</string>
+    <string name="tint" msgid="154435943863418434">"Ton"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
+    <string name="warmify" msgid="2404474974251014984">"Värme"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Rita på bilden om du vill doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Tryck och dra bilden om du vill vända den"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Knacka lätt om du vill ta bort röda ögon"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Tryck och dra bilden om du vill rotera den"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Tryck och dra bilden om du vill räta upp den"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-th/strings.xml b/samples/PhotoEditor/res/values-th/strings.xml
new file mode 100644
index 0000000..dcd2853
--- /dev/null
+++ b/samples/PhotoEditor/res/values-th/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"สตูดิโอถ่ายภาพ"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"ไม่สามารถโหลดภาพเพื่อแก้ไข"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"เกิดปัญหาในระหว่างการบันทึก"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"บันทึก <xliff:g id="FILENAME">%s</xliff:g> แล้ว"</string>
+    <string name="save_photo" msgid="3125109368779997862">"บันทึกภาพที่แก้ไขหรือไม่"</string>
+    <string name="yes" msgid="5402582493291792293">"ใช่"</string>
+    <string name="no" msgid="5595408018304861875">"ไม่"</string>
+    <string name="cancel" msgid="6286688759430767307">"ยกเลิก"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"แก้ไขแล้ว"</string>
+    <string name="artistic_group" msgid="573091775825402562">"ศิลปะ"</string>
+    <string name="color_group" msgid="3390757893475444688">"สี"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"การรับแสง"</string>
+    <string name="fix_group" msgid="971450155810247383">"คงที่"</string>
+    <string name="autofix" msgid="1223859191856172755">"ดีใจจัง"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"ครอสโพรเซส"</string>
+    <string name="documentary" msgid="50396326708699797">"สารคดี"</string>
+    <string name="doodle" msgid="1686409894518940990">"doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"ดูโอโทน"</string>
+    <string name="filllight" msgid="2644989991700022526">"เติมแสง"</string>
+    <string name="fisheye" msgid="6037488646928998921">"ฟิชอาย"</string>
+    <string name="flip" msgid="2357692401826287480">"ฟลิป"</string>
+    <string name="grain" msgid="7487585304579789098">"เนื้อฟิล์ม"</string>
+    <string name="grayscale" msgid="615169770671286699">"โทนสีเทา"</string>
+    <string name="highlight" msgid="3902653944386623972">"ไฮไลต์"</string>
+    <string name="lomoish" msgid="7793824845892532976">"แนวโลโม"</string>
+    <string name="negative" msgid="1985508917342811252">"สีตรงข้าม"</string>
+    <string name="posterize" msgid="4139212359561383385">"ลดจำนวนสีในภาพ"</string>
+    <string name="redeye" msgid="4958448806369928239">"ตาแดง"</string>
+    <string name="rotate" msgid="6607597269792373083">"หมุน"</string>
+    <string name="saturation" msgid="8621322012271169931">"ความอิ่มตัว"</string>
+    <string name="sepia" msgid="7978093531824705601">"ซีเปีย"</string>
+    <string name="shadow" msgid="8235188588101973090">"เงา"</string>
+    <string name="sharpen" msgid="8449662378104403230">"ทำให้ชัด"</string>
+    <string name="softfocus" msgid="6334228862566408303">"ซอฟต์โฟกัส"</string>
+    <string name="straighten" msgid="5217801513491493491">"ทำให้ตรง"</string>
+    <string name="temperature" msgid="3589958696423284897">"อุณหภูมิ"</string>
+    <string name="tint" msgid="154435943863418434">"แต้มสี"</string>
+    <string name="vignette" msgid="7648125924662648282">"วิกเน็ตต์"</string>
+    <string name="warmify" msgid="2404474974251014984">"ทำให้ภาพดูอบอุ่นขึ้น"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"วาดบนภาพเพื่อ doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"ลากภาพเพื่อพลิก"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"แตะเพื่อลบตาแดง"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"ลากภาพเพื่อหมุน"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"ลากภาพเพื่อยืดให้ตรง"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-tl/strings.xml b/samples/PhotoEditor/res/values-tl/strings.xml
new file mode 100644
index 0000000..bdfd5d5
--- /dev/null
+++ b/samples/PhotoEditor/res/values-tl/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Hindi ma-load ang larawan para sa pag-edit"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"May naganap na problema habang nagse-save"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Na-save ang <xliff:g id="FILENAME">%s</xliff:g>"</string>
+    <string name="save_photo" msgid="3125109368779997862">"I-save ang na-edit na larawan?"</string>
+    <string name="yes" msgid="5402582493291792293">"Oo"</string>
+    <string name="no" msgid="5595408018304861875">"Hindi"</string>
+    <string name="cancel" msgid="6286688759430767307">"Kanselahin"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Na-edit"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Artistic"</string>
+    <string name="color_group" msgid="3390757893475444688">"Kulay"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Exposure"</string>
+    <string name="fix_group" msgid="971450155810247383">"Ayusin"</string>
+    <string name="autofix" msgid="1223859191856172755">"Masuwerte"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"I-cross-process"</string>
+    <string name="documentary" msgid="50396326708699797">"Dokumentaryo"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
+    <string name="filllight" msgid="2644989991700022526">"Pampunong Ilaw"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
+    <string name="flip" msgid="2357692401826287480">"Baliktarin"</string>
+    <string name="grain" msgid="7487585304579789098">"Film Grain"</string>
+    <string name="grayscale" msgid="615169770671286699">"Grayscale"</string>
+    <string name="highlight" msgid="3902653944386623972">"Mga Highlight"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo-ish"</string>
+    <string name="negative" msgid="1985508917342811252">"Negative"</string>
+    <string name="posterize" msgid="4139212359561383385">"I-posterize"</string>
+    <string name="redeye" msgid="4958448806369928239">"Pulang Mata"</string>
+    <string name="rotate" msgid="6607597269792373083">"Paikutin"</string>
+    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
+    <string name="shadow" msgid="8235188588101973090">"Mga Shadow"</string>
+    <string name="sharpen" msgid="8449662378104403230">"I-sharpen"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Soft Focus"</string>
+    <string name="straighten" msgid="5217801513491493491">"Ituwid"</string>
+    <string name="temperature" msgid="3589958696423284897">"Temperatura"</string>
+    <string name="tint" msgid="154435943863418434">"Tint"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
+    <string name="warmify" msgid="2404474974251014984">"Gawing Warm"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Gumuhit sa larawan upang mag-doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"I-drag ang larawan upang i-flip ito"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Tapikin upang alisin ang mga pulang mata"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"I-drag ang larawan upang i-rotate ito"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"I-drag ang larawan upang iunat ito"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-tr/strings.xml b/samples/PhotoEditor/res/values-tr/strings.xml
new file mode 100644
index 0000000..b3df612
--- /dev/null
+++ b/samples/PhotoEditor/res/values-tr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Fotoğraf Stüdyosu"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Fotoğraf düzenlenmek üzere yüklenemiyor"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Kaydederken sorun oluştu"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> kaydedildi"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Düzenlenmiş fotoğraf kaydedilsin mi?"</string>
+    <string name="yes" msgid="5402582493291792293">"Evet"</string>
+    <string name="no" msgid="5595408018304861875">"Hayır"</string>
+    <string name="cancel" msgid="6286688759430767307">"İptal"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Düzenlendi"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Sanatsal"</string>
+    <string name="color_group" msgid="3390757893475444688">"Renk"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Pozlama"</string>
+    <string name="fix_group" msgid="971450155810247383">"Düzelt"</string>
+    <string name="autofix" msgid="1223859191856172755">"Şanslı Hissdyrm"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Çapraz Banyo"</string>
+    <string name="documentary" msgid="50396326708699797">"Belgesel"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"İki tonlu"</string>
+    <string name="filllight" msgid="2644989991700022526">"Dolgu Işığı"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Balıkgözü"</string>
+    <string name="flip" msgid="2357692401826287480">"Çevir"</string>
+    <string name="grain" msgid="7487585304579789098">"Film Greni"</string>
+    <string name="grayscale" msgid="615169770671286699">"Gri Tonlama"</string>
+    <string name="highlight" msgid="3902653944386623972">"Parlak noktalar"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo Tarzı"</string>
+    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
+    <string name="posterize" msgid="4139212359561383385">"Poster Efkt Uyg"</string>
+    <string name="redeye" msgid="4958448806369928239">"Kırmızı Göz"</string>
+    <string name="rotate" msgid="6607597269792373083">"Döndür"</string>
+    <string name="saturation" msgid="8621322012271169931">"Doygunluk"</string>
+    <string name="sepia" msgid="7978093531824705601">"Sepya Tonu"</string>
+    <string name="shadow" msgid="8235188588101973090">"Gölgeler"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Keskinleştir"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Yumuşak Odak"</string>
+    <string name="straighten" msgid="5217801513491493491">"Düzelt"</string>
+    <string name="temperature" msgid="3589958696423284897">"Sıcaklık"</string>
+    <string name="tint" msgid="154435943863418434">"Tonlama"</string>
+    <string name="vignette" msgid="7648125924662648282">"Vinyet"</string>
+    <string name="warmify" msgid="2404474974251014984">"Sıcak Göster"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Doodle için fotoğrafın üzerinde çizin"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Çevirmek için fotoğrafı sürükleyin"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Kırmızı gözleri kaldırmak için hafifçe vurun"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Döndürmek için fotoğrafı sürükleyin"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Düzleştirmek için fotoğrafı sürükleyin"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-uk/strings.xml b/samples/PhotoEditor/res/values-uk/strings.xml
new file mode 100644
index 0000000..8c71d57
--- /dev/null
+++ b/samples/PhotoEditor/res/values-uk/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Неможливо завантажити фотографію для редагування"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Під час збереження виникла проблема"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Файл \"<xliff:g id="FILENAME">%s</xliff:g>\" збережено"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Зберегти відредаговану фотографію?"</string>
+    <string name="yes" msgid="5402582493291792293">"Так"</string>
+    <string name="no" msgid="5595408018304861875">"Ні"</string>
+    <string name="cancel" msgid="6286688759430767307">"Скасувати"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Редаговано"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Художнє фото"</string>
+    <string name="color_group" msgid="3390757893475444688">"Колір"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Експозиція"</string>
+    <string name="fix_group" msgid="971450155810247383">"Виправлення"</string>
+    <string name="autofix" msgid="1223859191856172755">"Мені пощастить"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Крос-процес"</string>
+    <string name="documentary" msgid="50396326708699797">"Чорно-білі тони"</string>
+    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
+    <string name="duotone" msgid="8145893940788467106">"Два тони"</string>
+    <string name="filllight" msgid="2644989991700022526">"Доповн. світло"</string>
+    <string name="fisheye" msgid="6037488646928998921">"\"Риб’яче око\""</string>
+    <string name="flip" msgid="2357692401826287480">"Перевернути"</string>
+    <string name="grain" msgid="7487585304579789098">"Зернистість плівки"</string>
+    <string name="grayscale" msgid="615169770671286699">"Сірі тони"</string>
+    <string name="highlight" msgid="3902653944386623972">"Світлові ефекти"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Ломо-ефект"</string>
+    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
+    <string name="posterize" msgid="4139212359561383385">"Ефект плаката"</string>
+    <string name="redeye" msgid="4958448806369928239">"Червоні очі"</string>
+    <string name="rotate" msgid="6607597269792373083">"Обертання"</string>
+    <string name="saturation" msgid="8621322012271169931">"Насиченість"</string>
+    <string name="sepia" msgid="7978093531824705601">"Сепія"</string>
+    <string name="shadow" msgid="8235188588101973090">"Тіні"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Різкість"</string>
+    <string name="softfocus" msgid="6334228862566408303">"М’який фокус"</string>
+    <string name="straighten" msgid="5217801513491493491">"Вирівняти"</string>
+    <string name="temperature" msgid="3589958696423284897">"Температура"</string>
+    <string name="tint" msgid="154435943863418434">"Відтінок"</string>
+    <string name="vignette" msgid="7648125924662648282">"Віньєтка"</string>
+    <string name="warmify" msgid="2404474974251014984">"Тепліші тони"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Малюйте на фото, щоб створити doodle"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Перетягніть фото, щоб перевернути його"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Натисніть, щоб усунути ефект червоних очей"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Перетягніть фото, щоб обертати його"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Перетягніть фото, щоб вирівняти його"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-vi/strings.xml b/samples/PhotoEditor/res/values-vi/strings.xml
new file mode 100644
index 0000000..92f119f
--- /dev/null
+++ b/samples/PhotoEditor/res/values-vi/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Studio ảnh"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"Không thể tải ảnh để chỉnh sửa"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"Đã xảy ra lỗi trong khi lưu"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"Đã lưu <xliff:g id="FILENAME">%s</xliff:g>"</string>
+    <string name="save_photo" msgid="3125109368779997862">"Lưu ảnh đã chỉnh sửa?"</string>
+    <string name="yes" msgid="5402582493291792293">"Có"</string>
+    <string name="no" msgid="5595408018304861875">"Không"</string>
+    <string name="cancel" msgid="6286688759430767307">"Hủy"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Đã chỉnh sửa"</string>
+    <string name="artistic_group" msgid="573091775825402562">"Nghệ thuật"</string>
+    <string name="color_group" msgid="3390757893475444688">"Màu sắc"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"Phơi sáng"</string>
+    <string name="fix_group" msgid="971450155810247383">"Cố định"</string>
+    <string name="autofix" msgid="1223859191856172755">"Tự chỉnh ph.sáng"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"Tạo màu sắc khác lạ"</string>
+    <string name="documentary" msgid="50396326708699797">"Phim tài liệu"</string>
+    <string name="doodle" msgid="1686409894518940990">"Hình vẽ nguệch ngoạc"</string>
+    <string name="duotone" msgid="8145893940788467106">"Có hai màu"</string>
+    <string name="filllight" msgid="2644989991700022526">"Tô sáng"</string>
+    <string name="fisheye" msgid="6037488646928998921">"Mắt cá"</string>
+    <string name="flip" msgid="2357692401826287480">"Lật"</string>
+    <string name="grain" msgid="7487585304579789098">"Hiệu ứng hạt mỏng"</string>
+    <string name="grayscale" msgid="615169770671286699">"Thang màu xám"</string>
+    <string name="highlight" msgid="3902653944386623972">"Thêm điểm sáng"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Kiểu Lomo"</string>
+    <string name="negative" msgid="1985508917342811252">"Tạo âm bản"</string>
+    <string name="posterize" msgid="4139212359561383385">"Ảnh áp phích"</string>
+    <string name="redeye" msgid="4958448806369928239">"Mắt đỏ"</string>
+    <string name="rotate" msgid="6607597269792373083">"Xoay"</string>
+    <string name="saturation" msgid="8621322012271169931">"Bão hòa"</string>
+    <string name="sepia" msgid="7978093531824705601">"Nâu đỏ"</string>
+    <string name="shadow" msgid="8235188588101973090">"Bóng"</string>
+    <string name="sharpen" msgid="8449662378104403230">"Làm sắc nét"</string>
+    <string name="softfocus" msgid="6334228862566408303">"Tiêu điểm mềm"</string>
+    <string name="straighten" msgid="5217801513491493491">"Làm thẳng"</string>
+    <string name="temperature" msgid="3589958696423284897">"Nhiệt độ"</string>
+    <string name="tint" msgid="154435943863418434">"Phủ màu"</string>
+    <string name="vignette" msgid="7648125924662648282">"Làm mờ nét ảnh"</string>
+    <string name="warmify" msgid="2404474974251014984">"Làm nóng"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"Kéo trên ảnh để vẽ ngoạch ngoạc"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"Kéo ảnh để lật"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"Chạm để loại bỏ mắt đỏ"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"Kéo ảnh để xoay"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"Kéo ảnh để sắp thẳng"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-zh-rCN/strings.xml b/samples/PhotoEditor/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..50c7cc3
--- /dev/null
+++ b/samples/PhotoEditor/res/values-zh-rCN/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"照相馆"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"无法加载照片以供修改"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"保存时出现问题"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g>​已保存"</string>
+    <string name="save_photo" msgid="3125109368779997862">"要保存已修改的照片吗?"</string>
+    <string name="yes" msgid="5402582493291792293">"是"</string>
+    <string name="no" msgid="5595408018304861875">"否"</string>
+    <string name="cancel" msgid="6286688759430767307">"取消"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"已修改"</string>
+    <string name="artistic_group" msgid="573091775825402562">"艺术效果"</string>
+    <string name="color_group" msgid="3390757893475444688">"颜色"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"曝光"</string>
+    <string name="fix_group" msgid="971450155810247383">"修正"</string>
+    <string name="autofix" msgid="1223859191856172755">"修正曝光"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"交叉冲印"</string>
+    <string name="documentary" msgid="50396326708699797">"纪实"</string>
+    <string name="doodle" msgid="1686409894518940990">"涂鸦"</string>
+    <string name="duotone" msgid="8145893940788467106">"双色"</string>
+    <string name="filllight" msgid="2644989991700022526">"补光"</string>
+    <string name="fisheye" msgid="6037488646928998921">"鱼眼镜头"</string>
+    <string name="flip" msgid="2357692401826287480">"翻转"</string>
+    <string name="grain" msgid="7487585304579789098">"胶片颗粒"</string>
+    <string name="grayscale" msgid="615169770671286699">"灰度"</string>
+    <string name="highlight" msgid="3902653944386623972">"加亮区"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo 风格"</string>
+    <string name="negative" msgid="1985508917342811252">"反色"</string>
+    <string name="posterize" msgid="4139212359561383385">"色调分离"</string>
+    <string name="redeye" msgid="4958448806369928239">"红眼"</string>
+    <string name="rotate" msgid="6607597269792373083">"旋转"</string>
+    <string name="saturation" msgid="8621322012271169931">"饱和度"</string>
+    <string name="sepia" msgid="7978093531824705601">"深褐色"</string>
+    <string name="shadow" msgid="8235188588101973090">"阴影"</string>
+    <string name="sharpen" msgid="8449662378104403230">"锐化"</string>
+    <string name="softfocus" msgid="6334228862566408303">"柔焦"</string>
+    <string name="straighten" msgid="5217801513491493491">"拉直"</string>
+    <string name="temperature" msgid="3589958696423284897">"色温"</string>
+    <string name="tint" msgid="154435943863418434">"着色"</string>
+    <string name="vignette" msgid="7648125924662648282">"晕影"</string>
+    <string name="warmify" msgid="2404474974251014984">"暖色化"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"在照片上绘制即可涂鸦"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"拖动照片可将其翻转"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"点按可消除红眼"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"拖动照片可将其旋转"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"拖动照片可将其拉直"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values-zh-rTW/strings.xml b/samples/PhotoEditor/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..aada7e2
--- /dev/null
+++ b/samples/PhotoEditor/res/values-zh-rTW/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="6794833895507358569">"Photo Studio"</string>
+    <string name="loading_failure" msgid="8575831322115054185">"無法載入要編輯的相片"</string>
+    <string name="saving_failure" msgid="3116717307036082369">"儲存時發生問題"</string>
+    <string name="photo_saved" msgid="5093564498138145422">"<xliff:g id="FILENAME">%s</xliff:g> 已儲存"</string>
+    <string name="save_photo" msgid="3125109368779997862">"要儲存已編輯的相片嗎?"</string>
+    <string name="yes" msgid="5402582493291792293">"是"</string>
+    <string name="no" msgid="5595408018304861875">"否"</string>
+    <string name="cancel" msgid="6286688759430767307">"取消"</string>
+    <string name="edited_photo_bucket_name" msgid="3777746536831799890">"已編輯"</string>
+    <string name="artistic_group" msgid="573091775825402562">"藝術"</string>
+    <string name="color_group" msgid="3390757893475444688">"色彩"</string>
+    <string name="exposure_group" msgid="7019148007708100078">"曝光"</string>
+    <string name="fix_group" msgid="971450155810247383">"修正"</string>
+    <string name="autofix" msgid="1223859191856172755">"好幸運"</string>
+    <string name="crossprocess" msgid="4173724489001742342">"融合處理"</string>
+    <string name="documentary" msgid="50396326708699797">"黑白紀錄片"</string>
+    <string name="doodle" msgid="1686409894518940990">"塗鴉"</string>
+    <string name="duotone" msgid="8145893940788467106">"雙色調"</string>
+    <string name="filllight" msgid="2644989991700022526">"調整亮度"</string>
+    <string name="fisheye" msgid="6037488646928998921">"魚眼"</string>
+    <string name="flip" msgid="2357692401826287480">"翻轉"</string>
+    <string name="grain" msgid="7487585304579789098">"膠片顆粒"</string>
+    <string name="grayscale" msgid="615169770671286699">"灰階"</string>
+    <string name="highlight" msgid="3902653944386623972">"強光"</string>
+    <string name="lomoish" msgid="7793824845892532976">"Lomo 風格"</string>
+    <string name="negative" msgid="1985508917342811252">"負片"</string>
+    <string name="posterize" msgid="4139212359561383385">"色調分離"</string>
+    <string name="redeye" msgid="4958448806369928239">"紅眼"</string>
+    <string name="rotate" msgid="6607597269792373083">"旋轉"</string>
+    <string name="saturation" msgid="8621322012271169931">"飽和度"</string>
+    <string name="sepia" msgid="7978093531824705601">"深褐色"</string>
+    <string name="shadow" msgid="8235188588101973090">"陰影"</string>
+    <string name="sharpen" msgid="8449662378104403230">"銳化"</string>
+    <string name="softfocus" msgid="6334228862566408303">"柔焦"</string>
+    <string name="straighten" msgid="5217801513491493491">"拉正"</string>
+    <string name="temperature" msgid="3589958696423284897">"色溫"</string>
+    <string name="tint" msgid="154435943863418434">"著色"</string>
+    <string name="vignette" msgid="7648125924662648282">"暈影"</string>
+    <string name="warmify" msgid="2404474974251014984">"加強暖色"</string>
+    <string name="doodle_tooltip" msgid="2902117272374362915">"在相片上畫圖即可塗鴉"</string>
+    <string name="flip_tooltip" msgid="2700943256714731737">"拖曳照片即可翻轉"</string>
+    <string name="redeye_tooltip" msgid="9112774042113471358">"輕按即可消除紅眼"</string>
+    <string name="rotate_tooltip" msgid="7008602969130734229">"拖曳照片即可旋轉"</string>
+    <string name="straighten_tooltip" msgid="4846317027139212339">"拖曳照片即可拉正"</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values/arrays.xml b/samples/PhotoEditor/res/values/arrays.xml
new file mode 100644
index 0000000..9877b98
--- /dev/null
+++ b/samples/PhotoEditor/res/values/arrays.xml
@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string-array name="autofix_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="autofix_icons">
+        <item>@drawable/autofix_off</item>
+        <item>@drawable/autofix_on</item>
+    </array>
+
+    <string-array name="crop_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="crop_icons">
+        <item>@drawable/crop_off</item>
+        <item>@drawable/crop_on</item>
+    </array>
+
+    <string-array name="crossprocess_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="crossprocess_icons">
+        <item>@drawable/crossprocess_off</item>
+        <item>@drawable/crossprocess_on</item>
+    </array>
+
+    <string-array name="documentary_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="documentary_icons">
+        <item>@drawable/documentary_off</item>
+        <item>@drawable/documentary_on</item>
+    </array>
+
+    <string-array name="doodle_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="doodle_icons">
+        <item>@drawable/doodle_off</item>
+        <item>@drawable/doodle_on</item>
+    </array>
+
+    <string-array name="duotone_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="duotone_icons">
+        <item>@drawable/duotone_off</item>
+        <item>@drawable/duotone_on</item>
+    </array>
+
+    <string-array name="filllight_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="filllight_icons">
+        <item>@drawable/filllight_off</item>
+        <item>@drawable/filllight_on</item>
+    </array>
+
+    <string-array name="fisheye_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="fisheye_icons">
+        <item>@drawable/fisheye_off</item>
+        <item>@drawable/fisheye_on</item>
+    </array>
+
+    <string-array name="flip_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="flip_icons">
+        <item>@drawable/flip_off</item>
+        <item>@drawable/flip_on</item>
+    </array>
+
+    <string-array name="grain_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="grain_icons">
+        <item>@drawable/grain_off</item>
+        <item>@drawable/grain_on</item>
+    </array>
+
+    <string-array name="grayscale_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="grayscale_icons">
+        <item>@drawable/grayscale_off</item>
+        <item>@drawable/grayscale_on</item>
+    </array>
+
+    <string-array name="highlight_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="highlight_icons">
+        <item>@drawable/highlight_off</item>
+        <item>@drawable/highlight_on</item>
+    </array>
+
+    <string-array name="lomoish_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="lomoish_icons">
+        <item>@drawable/lomoish_off</item>
+        <item>@drawable/lomoish_on</item>
+    </array>
+
+    <string-array name="negative_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="negative_icons">
+        <item>@drawable/negative_off</item>
+        <item>@drawable/negative_on</item>
+    </array>
+
+    <string-array name="posterize_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="posterize_icons">
+        <item>@drawable/posterize_off</item>
+        <item>@drawable/posterize_on</item>
+    </array>
+
+    <string-array name="redeye_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="redeye_icons">
+        <item>@drawable/redeye_off</item>
+        <item>@drawable/redeye_on</item>
+    </array>
+
+    <string-array name="rotate_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="rotate_icons">
+        <item>@drawable/rotate_off</item>
+        <item>@drawable/rotate_on</item>
+    </array>
+
+    <string-array name="saturation_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="saturation_icons">
+        <item>@drawable/saturation_off</item>
+        <item>@drawable/saturation_on</item>
+    </array>
+
+    <string-array name="sepia_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="sepia_icons">
+        <item>@drawable/sepia_off</item>
+        <item>@drawable/sepia_on</item>
+    </array>
+
+    <string-array name="shadow_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="shadow_icons">
+        <item>@drawable/shadow_off</item>
+        <item>@drawable/shadow_on</item>
+    </array>
+
+    <string-array name="sharpen_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="sharpen_icons">
+        <item>@drawable/sharpen_off</item>
+        <item>@drawable/sharpen_on</item>
+    </array>
+
+    <string-array name="softfocus_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="softfocus_icons">
+        <item>@drawable/softfocus_off</item>
+        <item>@drawable/softfocus_on</item>
+    </array>
+
+    <string-array name="straighten_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="straighten_icons">
+        <item>@drawable/straighten_off</item>
+        <item>@drawable/straighten_on</item>
+    </array>
+
+    <string-array name="temperature_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="temperature_icons">
+        <item>@drawable/colortemperature_off</item>
+        <item>@drawable/colortemperature_on</item>
+    </array>
+
+    <string-array name="tint_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="tint_icons">
+        <item>@drawable/tint_off</item>
+        <item>@drawable/tint_on</item>
+    </array>
+
+    <string-array name="vignette_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="vignette_icons">
+        <item>@drawable/vignette_off</item>
+        <item>@drawable/vignette_on</item>
+    </array>
+
+    <string-array name="warmify_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="warmify_icons">
+        <item>@drawable/warmify_off</item>
+        <item>@drawable/warmify_on</item>
+    </array>
+
+    <string-array name="undo_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="undo_icons">
+        <item>@drawable/undo</item>
+        <item>@drawable/undo</item>
+    </array>
+
+    <string-array name="redo_modes" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="redo_icons">
+        <item>@drawable/redo</item>
+        <item>@drawable/redo</item>
+    </array>
+
+    <array name="color_picker_wheel_colors">
+        <item>@color/color_picker_preset_color1</item>
+        <item>@color/color_picker_preset_color2</item>
+        <item>@color/color_picker_preset_color3</item>
+        <item>@color/color_picker_preset_color4</item>
+        <item>@color/color_picker_preset_color5</item>
+        <item>@color/color_picker_preset_color6</item>
+        <item>@color/color_picker_preset_color7</item>
+        <item>@color/color_picker_preset_color8</item>
+        <item>@color/color_picker_preset_color9</item>
+        <item>@color/color_picker_preset_color10</item>
+        <item>@color/color_picker_preset_color11</item>
+        <item>@color/color_picker_preset_color12</item>
+        <item>@color/color_picker_preset_color13</item>
+        <item>@color/color_picker_preset_color14</item>
+        <item>@color/color_picker_preset_color15</item>
+        <item>@color/color_picker_preset_color16</item>
+        <item>@color/color_picker_preset_color17</item>
+        <item>@color/color_picker_preset_color18</item>
+        <item>@color/color_picker_preset_color19</item>
+        <item>@color/color_picker_preset_color20</item>
+        <item>@color/color_picker_preset_color21</item>
+        <item>@color/color_picker_preset_color22</item>
+        <item>@color/color_picker_preset_color23</item>
+        <item>@color/color_picker_preset_color24</item>
+    </array>
+
+</resources>
diff --git a/samples/PhotoEditor/res/values/attrs.xml b/samples/PhotoEditor/res/values/attrs.xml
new file mode 100644
index 0000000..052534c
--- /dev/null
+++ b/samples/PhotoEditor/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <declare-styleable name="IconIndicator">
+        <attr name="icons" format="reference" />
+        <attr name="modes" format="reference" />
+    </declare-styleable>
+</resources>
diff --git a/samples/PhotoEditor/res/values/colors.xml b/samples/PhotoEditor/res/values/colors.xml
new file mode 100644
index 0000000..f699c1b
--- /dev/null
+++ b/samples/PhotoEditor/res/values/colors.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <color name="scale_wheel_interior_color">#40808080</color>
+
+    <color name="color_picker_indicator_color">#ffff3377</color>
+    <color name="color_picker_border_color">#ffbbbbbb</color>
+
+    <color name="color_picker_preset_color1">#ddff1e00</color>
+    <color name="color_picker_preset_color2">#ddff5b00</color>
+    <color name="color_picker_preset_color3">#ddff9900</color>
+    <color name="color_picker_preset_color4">#ddffd600</color>
+    <color name="color_picker_preset_color5">#ddeaff00</color>
+    <color name="color_picker_preset_color6">#ddadff00</color>
+    <color name="color_picker_preset_color7">#dd70ff00</color>
+    <color name="color_picker_preset_color8">#dd32ff00</color>
+    <color name="color_picker_preset_color9">#dd00ff0a</color>
+    <color name="color_picker_preset_color10">#dd00ff47</color>
+    <color name="color_picker_preset_color11">#dd00ff84</color>
+    <color name="color_picker_preset_color12">#dd00ffc1</color>
+    <color name="color_picker_preset_color13">#dd00ffff</color>
+    <color name="color_picker_preset_color14">#dd00c1ff</color>
+    <color name="color_picker_preset_color15">#dd0084ff</color>
+    <color name="color_picker_preset_color16">#dd0047ff</color>
+    <color name="color_picker_preset_color17">#dd000aff</color>
+    <color name="color_picker_preset_color18">#dd3300ff</color>
+    <color name="color_picker_preset_color19">#dd7000ff</color>
+    <color name="color_picker_preset_color20">#ddad00ff</color>
+    <color name="color_picker_preset_color21">#ddea00ff</color>
+    <color name="color_picker_preset_color22">#ddff00d6</color>
+    <color name="color_picker_preset_color23">#ddff0098</color>
+    <color name="color_picker_preset_color24">#ddff005b</color>
+</resources>
diff --git a/samples/PhotoEditor/res/values/dimen.xml b/samples/PhotoEditor/res/values/dimen.xml
new file mode 100755
index 0000000..cbadea0
--- /dev/null
+++ b/samples/PhotoEditor/res/values/dimen.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="group_cotent_padding_top">10dp</dimen>
+    <dimen name="group_cotent_padding_bottom">15dp</dimen>
+    <dimen name="group_header_height">48dp</dimen>
+    <dimen name="group_header_padding_bottom">10dp</dimen>
+    <dimen name="group_header_padding_left">11dp</dimen>
+    <dimen name="group_header_padding_right">5dp</dimen>
+    <dimen name="group_arrow_size">20dp</dimen>
+    <dimen name="group_label_text_size">15sp</dimen>
+    <dimen name="effect_label_text_size">14sp</dimen>
+    <dimen name="effect_icon_size">92dp</dimen>
+    <dimen name="effect_padding_top">10dp</dimen>
+    <dimen name="effect_padding_bottom">15dp</dimen>
+    <dimen name="effects_bar_width">124dp</dimen>
+    <dimen name="effects_bar_margin_top">4dp</dimen>
+    <dimen name="effects_bar_margin_bottom">4dp</dimen>
+    <dimen name="action_bar_height">50dp</dimen>
+    <dimen name="action_bar_text_size">18sp</dimen>
+    <dimen name="action_bar_arrow_padding_top">17dp</dimen>
+    <dimen name="action_bar_arrow_padding_bottom">17dp</dimen>
+    <dimen name="action_bar_arrow_padding_left">3dp</dimen>
+    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
+    <dimen name="action_bar_icon_padding_top">5dp</dimen>
+    <dimen name="action_bar_icon_padding_bottom">5dp</dimen>
+    <dimen name="action_bar_icon_padding_left">0dp</dimen>
+    <dimen name="action_bar_icon_padding_right">8dp</dimen>
+    <dimen name="action_button_padding_top">7dp</dimen>
+    <dimen name="action_button_padding_bottom">7dp</dimen>
+    <dimen name="action_button_padding_left">22dp</dimen>
+    <dimen name="action_button_padding_right">22dp</dimen>
+    <dimen name="action_button_padding_right_most">45dp</dimen>
+    <dimen name="wheel_layout_marginLeft">-48dp</dimen>
+    <dimen name="wheel_layout_size">240dp</dimen>
+    <dimen name="wheel_thumb_size">51dp</dimen>
+    <dimen name="crop_indicator_size">32dp</dimen>
+</resources>
diff --git a/samples/PhotoEditor/res/values/strings.xml b/samples/PhotoEditor/res/values/strings.xml
new file mode 100644
index 0000000..c4f587c
--- /dev/null
+++ b/samples/PhotoEditor/res/values/strings.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Application name appearing in the application launcher. [CHAR LIMIT=NONE] -->
+    <string name="app_name">Photo Studio</string>
+
+    <!-- Toast shown when selected photo could not be loaded. [CHAR LIMIT=50] -->
+    <string name="loading_failure">Cannot load the photo for editing</string>
+
+    <!-- Toast shown when edited photo could not be saved. [CHAR LIMIT=50] -->
+    <string name="saving_failure">A problem occurred during saving</string>
+
+    <!-- Toast shown when edited photo is successfully saved with filename %s [CHAR LIMIT=50] -->
+    <string name="photo_saved"><xliff:g id="filename">%s</xliff:g> saved</string>
+
+    <!-- Dialog message prompted when the user is abandoning unsaved changes. [CHAR LIMIT=50] -->
+    <string name="save_photo">Save edited photo?</string>
+
+    <!-- Dialog yes button for the user to accept text presented in a dialog. [CHAR LIMIT=12] -->
+    <string name="yes">Yes</string>
+
+    <!-- Dialog no button for the user to decline text presented in a dialog. [CHAR LIMIT=12] -->
+    <string name="no">No</string>
+
+    <!-- Dialog cancel button for the user to cancel a dialog. [CHAR LIMIT=12] -->
+    <string name="cancel">Cancel</string>
+
+    <!-- Directory name where edited photo will be saved in the storage. [CHAR LIMIT=12] -->
+    <string name="edited_photo_bucket_name">Edited</string>
+
+    <!-- Menu label to group all artistic-related photo effects, e.g. vignette, posterize, etc.
+         [CHAR LIMIT=11] -->
+    <string name="artistic_group">Artistic</string>
+
+    <!-- Menu label to group all color-related photo effects, e.g. color-temperature, tint, etc.
+         [CHAR LIMIT=11] -->
+    <string name="color_group">Color</string>
+
+    <!-- Menu label to group all exposure-related photo effects, e.g. fill-light, highlight, etc.
+         [CHAR LIMIT=11] -->
+    <string name="exposure_group">Exposure</string>
+
+    <!-- Menu label to group all fix-related photo effects, e.g. straighten, red-eye removal, etc.
+         [CHAR LIMIT=11] -->
+    <string name="fix_group">Fix</string>
+
+    <!-- Name for the photo effect 'Feel Lucky' that auto-fixes exposure. [CHAR LIMIT=15] -->
+    <string name="autofix">Feel Lucky</string>
+
+    <!-- Name for the photo effect that crops photo. [CHAR LIMIT=15] -->
+    <string name="crop">Crop</string>
+
+    <!-- Name for the photo effect that applies cross-process effect. [CHAR LIMIT=15] -->
+    <string name="crossprocess">Cross-process</string>
+
+    <!-- Name for the photo effect that applies black/white documentary styles. [CHAR LIMIT=15] -->
+    <string name="documentary">Documentary</string>
+
+    <!-- Name for the photo effect that doodles on photo. [CHAR LIMIT=15] -->
+    <string name="doodle">Doodle</string>
+
+    <!-- Name for the photo effect that makes photo only two color tones. [CHAR LIMIT=15] -->
+    <string name="duotone">Duo-tone</string>
+
+    <!-- Name for the photo effect that fills back-light. [CHAR LIMIT=15] -->
+    <string name="filllight">Fill Light</string>
+
+    <!-- Name for the photo effect that applies fisheye lens distortion. [CHAR LIMIT=15] -->
+    <string name="fisheye">Fisheye</string>
+
+    <!-- Name for the photo effect that flips photo vertically or horizontally. [CHAR LIMIT=15] -->
+    <string name="flip">Flip</string>
+
+    <!-- Name for the photo effect that adds grains and noises. [CHAR LIMIT=15] -->
+    <string name="grain">Film Grain</string>
+
+    <!-- Name for the photo effect that converts colors to grayscales. [CHAR LIMIT=15] -->
+    <string name="grayscale">Grayscale</string>
+
+    <!-- Name for the photo effect that adds highlights. [CHAR LIMIT=15] -->
+    <string name="highlight">Highlights</string>
+
+    <!-- Name for the photo effect that applies lomo-camera styles. [CHAR LIMIT=15] -->
+    <string name="lomoish">Lomo-ish</string>
+
+    <!-- Name for the photo effect that inverts photo to negative images. [CHAR LIMIT=15] -->
+    <string name="negative">Negative</string>
+
+    <!-- Name for the photo effect that applies posterization. [CHAR LIMIT=15] -->
+    <string name="posterize">Posterize</string>
+
+    <!-- Name for the photo effect that remove red eyes. [CHAR LIMIT=15] -->
+    <string name="redeye">Red Eye</string>
+
+    <!-- Name for the photo effect that rotates photo. [CHAR LIMIT=15] -->
+    <string name="rotate">Rotate</string>
+
+    <!-- Name for the photo effect that adjusts color saturation. [CHAR LIMIT=15] -->
+    <string name="saturation">Saturation</string>
+
+    <!-- Name for the photo effect that applies sepia color toning. [CHAR LIMIT=15] -->
+    <string name="sepia">Sepia</string>
+
+    <!-- Name for the photo effect that adds shadows. [CHAR LIMIT=15] -->
+    <string name="shadow">Shadows</string>
+
+    <!-- Name for the photo effect that sharpens blurred photo. [CHAR LIMIT=15] -->
+    <string name="sharpen">Sharpen</string>
+
+    <!-- Name for the photo effect that applies soft-focus. [CHAR LIMIT=15] -->
+    <string name="softfocus">Soft Focus</string>
+
+    <!-- Name for the photo effect that straightens crooked photo. [CHAR LIMIT=15] -->
+    <string name="straighten">Straighten</string>
+
+    <!-- Name for the photo effect that adjusts color-temperature. [CHAR LIMIT=15] -->
+    <string name="temperature">Temperature</string>
+
+    <!-- Name for the photo effect that tints photo with various colors. [CHAR LIMIT=15] -->
+    <string name="tint">Tint</string>
+
+    <!-- Name for the photo effect that fades off photo edges. [CHAR LIMIT=15] -->
+    <string name="vignette">Vignette</string>
+
+    <!-- Name for the photo effect that applies warmify color toning. [CHAR LIMIT=15] -->
+    <string name="warmify">Warmify</string>
+
+    <!-- Tool-tip toast shown when the user chooses to crop photo. [CHAR LIMIT=50] -->
+    <string name="crop_tooltip">Drag photo to crop it</string>
+
+    <!-- Tool-tip toast shown when the user chooses to doodle on photo. [CHAR LIMIT=50] -->
+    <string name="doodle_tooltip">Draw on photo to doodle</string>
+
+    <!-- Tool-tip toast shown when the user chooses to flip photo. [CHAR LIMIT=50] -->
+    <string name="flip_tooltip">Drag photo to flip it</string>
+
+    <!-- Tool-tip toast shown when the user chooses to remove red eyes. [CHAR LIMIT=50] -->
+    <string name="redeye_tooltip">Tap to remove red eyes</string>
+
+    <!-- Tool-tip toast shown when the user chooses to rotate photo. [CHAR LIMIT=50] -->
+    <string name="rotate_tooltip">Drag photo to rotate it</string>
+
+    <!-- Tool-tip toast shown when the user chooses to straighten photo. [CHAR LIMIT=50] -->
+    <string name="straighten_tooltip">Drag photo to straighten it</string>
+</resources>
diff --git a/samples/PhotoEditor/res/values/styles.xml b/samples/PhotoEditor/res/values/styles.xml
new file mode 100644
index 0000000..35bca10
--- /dev/null
+++ b/samples/PhotoEditor/res/values/styles.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="GroupHeader">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">@dimen/group_header_height</item>
+        <item name="android:paddingBottom">@dimen/group_header_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/group_header_padding_left</item>
+        <item name="android:paddingRight">@dimen/group_header_padding_right</item>
+        <item name="android:background">@drawable/group_header</item>
+    </style>
+    <style name="GroupArrow">
+        <item name="android:layout_width">@dimen/group_arrow_size</item>
+        <item name="android:layout_height">@dimen/group_arrow_size</item>
+        <item name="android:layout_gravity">center_vertical|right</item>
+        <item name="android:paddingTop">2dp</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:src">@drawable/arrow_down</item>
+    </style>
+    <style name="GroupLabel">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:textSize">@dimen/group_label_text_size</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+    <style name="GroupContent" parent="@style/EffectsBarLinearLayout">
+        <item name="android:paddingTop">@dimen/group_cotent_padding_top</item>
+        <item name="android:paddingBottom">@dimen/group_cotent_padding_bottom</item>
+    </style>
+    <style name="EffectIcon">
+        <item name="android:layout_width">@dimen/effect_icon_size</item>
+        <item name="android:layout_height">@dimen/effect_icon_size</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+    <style name="EffectLabel">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textSize">@dimen/effect_label_text_size</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+    <style name="Effect">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:paddingTop">@dimen/effect_padding_top</item>
+        <item name="android:paddingBottom">@dimen/effect_padding_bottom</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+    <style name="EffectsBarLinearLayout">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+    <style name="EffectsBar">
+        <item name="android:layout_width">@dimen/effects_bar_width</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_alignParentRight">true</item>
+        <item name="android:layout_marginTop">@dimen/effects_bar_margin_top</item>
+        <item name="android:layout_marginBottom">@dimen/effects_bar_margin_bottom</item>
+        <item name="android:scrollbars">none</item>
+    </style>
+    <style name="ActionBar">
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">@dimen/action_bar_height</item>
+        <item name="android:background">@null</item>
+    </style>
+    <style name="ActionBarTranslucent">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:background">@drawable/actionbar_translucent</item>
+    </style>
+    <style name="ActionBarLinearLayout">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:orientation">horizontal</item>
+    </style>
+    <style name="ActionBarBackLinearLayout" parent="@style/ActionBarLinearLayout">
+        <item name="android:clickable">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="ActionBarImageView">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:scaleType">centerInside</item>
+        <item name="android:adjustViewBounds">true</item>
+    </style>
+    <style name="ActionBarArrow" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_bar_arrow_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_bar_arrow_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_bar_arrow_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_bar_arrow_padding_right</item>
+        <item name="android:src">@drawable/arrow_back</item>
+    </style>
+    <style name="ActionBarIcon" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_bar_icon_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_bar_icon_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_bar_icon_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_bar_icon_padding_right</item>
+        <item name="android:src">@drawable/icon</item>
+    </style>
+    <style name="ActionBarText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:textSize">@dimen/action_bar_text_size</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+    <style name="ActionButton" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_button_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_button_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_button_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_button_padding_right</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="RightmostActionButton" parent="@style/ActionButton">
+        <item name="android:paddingRight">@dimen/action_button_padding_right_most</item>
+    </style>
+    <style name="Wheel">
+        <item name="android:layout_width">@dimen/wheel_layout_size</item>
+        <item name="android:layout_height">@dimen/wheel_layout_size</item>
+        <item name="android:layout_alignParentLeft">true</item>
+        <item name="android:layout_marginLeft">@dimen/wheel_layout_marginLeft</item>
+        <item name="android:background">@null</item>
+        <item name="android:visibility">invisible</item>
+    </style>
+    <style name="FullscreenToolView">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:visibility">invisible</item>
+    </style>
+    <style name="SpinnerProgressDialog">
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+</resources>
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/ActionBar.java b/samples/PhotoEditor/src/com/android/photoeditor/ActionBar.java
new file mode 100644
index 0000000..4465741
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/ActionBar.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ViewSwitcher;
+
+/**
+ * Action bar that contains buttons such as undo, redo, save, etc. and listens to stack changes for
+ * enabling/disabling buttons.
+ */
+public class ActionBar extends ViewSwitcher implements FilterStack.StackListener {
+
+    /**
+     * Listener of action button clicked.
+     */
+    public interface ActionBarListener {
+
+        void onQuickview(boolean on);
+
+        void onUndo();
+
+        void onRedo();
+
+        void onSave();
+    }
+
+    private static final int ENABLE_BUTTON = 1;
+    private static final int ENABLED_ALPHA = 255;
+    private static final int DISABLED_ALPHA = 120;
+
+    private final Handler handler;
+    private ImageButton save;
+    private ImageButton undo;
+    private ImageButton redo;
+    private ImageButton quickview;
+
+    public ActionBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        handler = new Handler() {
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ENABLE_BUTTON:
+                        boolean canUndo = (msg.arg1 > 0);
+                        boolean canRedo = (msg.arg2 > 0);
+                        enableButton(quickview, canUndo);
+                        enableButton(save, canUndo);
+                        enableButton(undo, canUndo);
+                        enableButton(redo, canRedo);
+                        break;
+                }
+            }
+        };
+    }
+
+    /**
+     * Initializes with a non-null ActionBarListener.
+     */
+    public void initialize(final ActionBarListener listener) {
+        save = (ImageButton) findViewById(R.id.save_button);
+        save.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onSave();
+                }
+            }
+        });
+
+        undo = (ImageButton) findViewById(R.id.undo_button);
+        undo.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onUndo();
+                }
+            }
+        });
+
+        redo = (ImageButton) findViewById(R.id.redo_button);
+        redo.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onRedo();
+                }
+            }
+        });
+
+        quickview = (ImageButton) findViewById(R.id.quickview_button);
+        quickview.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    ActionBar.this.showNext();
+                    listener.onQuickview(true);
+                }
+            }
+        });
+
+        View quickviewOn = findViewById(R.id.quickview_on_button);
+        quickviewOn.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    ActionBar.this.showNext();
+                    listener.onQuickview(false);
+                }
+            }
+        });
+
+        resetButtons();
+    }
+
+    public void resetButtons() {
+        // Disable buttons immediately instead of waiting for ENABLE_BUTTON messages which may
+        // happen some time later after stack changes.
+        enableButton(save, false);
+        enableButton(undo, false);
+        enableButton(redo, false);
+        enableButton(quickview, false);
+    }
+
+    public void disableSave() {
+        enableButton(save, false);
+    }
+
+    private void enableButton(ImageButton button, boolean enabled) {
+        button.setEnabled(enabled);
+        button.setAlpha(enabled ? ENABLED_ALPHA : DISABLED_ALPHA);
+    }
+
+    @Override
+    public void onStackChanged(boolean canUndo, boolean canRedo) {
+        // Listens to stack changes that may come from the worker thread; send messages to enable
+        // buttons only in the UI thread.
+        handler.sendMessage(handler.obtainMessage(ENABLE_BUTTON, canUndo ? 1 : 0, canRedo ? 1 : 0));
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/BitmapUtils.java b/samples/PhotoEditor/src/com/android/photoeditor/BitmapUtils.java
new file mode 100644
index 0000000..8678f10
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/BitmapUtils.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Utils for bitmap operations.
+ */
+public class BitmapUtils {
+
+    private static final String TAG = "BitmapUtils";
+    private static final int DEFAULT_COMPRESS_QUALITY = 90;
+    private static final int INDEX_ORIENTATION = 0;
+
+    private static final String[] IMAGE_PROJECTION = new String[] {
+        ImageColumns.ORIENTATION
+    };
+
+    private final Context context;
+
+    public BitmapUtils(Context context) {
+        this.context = context;
+    }
+
+    /**
+     * Returns an immutable bitmap from subset of source bitmap transformed by the given matrix.
+     */
+    public static Bitmap createBitmap(Bitmap bitmap, Matrix m) {
+        // TODO: Re-implement createBitmap to avoid ARGB -> RBG565 conversion on platforms
+        // prior to honeycomb.
+        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
+    }
+
+    private void closeStream(Closeable stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private Rect getBitmapBounds(Uri uri) {
+        Rect bounds = new Rect();
+        InputStream is = null;
+
+        try {
+            is = context.getContentResolver().openInputStream(uri);
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, options);
+
+            bounds.right = options.outWidth;
+            bounds.bottom = options.outHeight;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(is);
+        }
+
+        return bounds;
+    }
+
+    private int getOrientation(Uri uri) {
+        int orientation = 0;
+        Cursor cursor = context.getContentResolver().query(uri, IMAGE_PROJECTION, null, null, null);
+        if ((cursor != null) && cursor.moveToNext()) {
+            orientation = cursor.getInt(INDEX_ORIENTATION);
+        }
+        return orientation;
+    }
+
+    /**
+     * Decodes immutable bitmap that keeps aspect-ratio and spans most within the given rectangle.
+     */
+    private Bitmap decodeBitmap(Uri uri, int width, int height) {
+        InputStream is = null;
+        Bitmap bitmap = null;
+
+        try {
+            // TODO: Take max pixels allowed into account for calculation to avoid possible OOM.
+            Rect bounds = getBitmapBounds(uri);
+            int sampleSize = Math.max(bounds.width() / width, bounds.height() / height);
+            sampleSize = Math.min(sampleSize,
+                    Math.max(bounds.width() / height, bounds.height() / width));
+
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inSampleSize = Math.max(sampleSize, 1);
+            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+            is = context.getContentResolver().openInputStream(uri);
+            bitmap = BitmapFactory.decodeStream(is, null, options);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "FileNotFoundException: " + uri);
+        } finally {
+            closeStream(is);
+        }
+
+        // Scale down the sampled bitmap if it's still larger than the desired dimension.
+        if (bitmap != null) {
+            float scale = Math.min((float) width / bitmap.getWidth(),
+                    (float) height / bitmap.getHeight());
+            scale = Math.max(scale, Math.min((float) height / bitmap.getWidth(),
+                    (float) width / bitmap.getHeight()));
+            if (scale < 1) {
+                Matrix m = new Matrix();
+                m.setScale(scale, scale);
+                Bitmap transformed = createBitmap(bitmap, m);
+                bitmap.recycle();
+                return transformed;
+            }
+        }
+        return bitmap;
+    }
+
+    /**
+     * Gets decoded bitmap that keeps orientation as well.
+     */
+    public Bitmap getBitmap(Uri uri, int width, int height) {
+        Bitmap bitmap = decodeBitmap(uri, width, height);
+
+        // Rotate the decoded bitmap according to its orientation if it's necessary.
+        if (bitmap != null) {
+            int orientation = getOrientation(uri);
+            if (orientation != 0) {
+                Matrix m = new Matrix();
+                m.setRotate(orientation);
+                Bitmap transformed = createBitmap(bitmap, m);
+                bitmap.recycle();
+                return transformed;
+            }
+        }
+        return bitmap;
+    }
+
+    /**
+     * Saves the bitmap by given directory, filename, and format; if the directory is given null,
+     * then saves it under the cache directory.
+     */
+    public File saveBitmap(
+            Bitmap bitmap, String directory, String filename, CompressFormat format) {
+
+        if (directory == null) {
+            directory = context.getCacheDir().getAbsolutePath();
+        } else {
+            // Check if the given directory exists or try to create it.
+            File file = new File(directory);
+            if (!file.isDirectory() && !file.mkdirs()) {
+                return null;
+            }
+        }
+
+        File file = null;
+        OutputStream os = null;
+
+        try {
+            filename = (format == CompressFormat.PNG) ? filename + ".png" : filename + ".jpg";
+            file = new File(directory, filename);
+            os = new FileOutputStream(file);
+            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(os);
+        }
+        return file;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/EffectsBar.java b/samples/PhotoEditor/src/com/android/photoeditor/EffectsBar.java
new file mode 100644
index 0000000..63cbf3f
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/EffectsBar.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.photoeditor.actions.AutoFixAction;
+import com.android.photoeditor.actions.ColorTemperatureAction;
+import com.android.photoeditor.actions.CropAction;
+import com.android.photoeditor.actions.CrossProcessAction;
+import com.android.photoeditor.actions.DocumentaryAction;
+import com.android.photoeditor.actions.DoodleAction;
+import com.android.photoeditor.actions.DuotoneAction;
+import com.android.photoeditor.actions.FillLightAction;
+import com.android.photoeditor.actions.FilterAction;
+import com.android.photoeditor.actions.FisheyeAction;
+import com.android.photoeditor.actions.FlipAction;
+import com.android.photoeditor.actions.GrainAction;
+import com.android.photoeditor.actions.GrayscaleAction;
+import com.android.photoeditor.actions.HighlightAction;
+import com.android.photoeditor.actions.LomoishAction;
+import com.android.photoeditor.actions.NegativeAction;
+import com.android.photoeditor.actions.PosterizeAction;
+import com.android.photoeditor.actions.RedEyeAction;
+import com.android.photoeditor.actions.RotateAction;
+import com.android.photoeditor.actions.SaturationAction;
+import com.android.photoeditor.actions.SepiaAction;
+import com.android.photoeditor.actions.ShadowAction;
+import com.android.photoeditor.actions.SharpenAction;
+import com.android.photoeditor.actions.StraightenAction;
+import com.android.photoeditor.actions.TintAction;
+import com.android.photoeditor.actions.VignetteAction;
+import com.android.photoeditor.actions.WarmifyAction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Scroll view that contains all effects for editing photo by mapping each effect to trigger one
+ * corresponding FilterAction.
+ */
+public class EffectsBar extends ScrollView {
+
+    private final List<Effect> effects = new ArrayList<Effect>();
+    private TextView effectName;
+
+    public EffectsBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void initialize(FilterStack filterStack, PhotoView photoView, ViewGroup tools) {
+        effects.add(new Effect(R.id.autofix_effect,
+                new AutoFixAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.crop_effect,
+                new CropAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.crossprocess_effect,
+                new CrossProcessAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.documentary_effect,
+                new DocumentaryAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.doodle_effect,
+                new DoodleAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.duotone_effect,
+                new DuotoneAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.filllight_effect,
+                new FillLightAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.fisheye_effect,
+                new FisheyeAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.flip_effect,
+                new FlipAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.grain_effect,
+                new GrainAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.grayscale_effect,
+                new GrayscaleAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.highlight_effect,
+                new HighlightAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.lomoish_effect,
+                new LomoishAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.negative_effect,
+                new NegativeAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.posterize_effect,
+                new PosterizeAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.redeye_effect,
+                new RedEyeAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.rotate_effect,
+                new RotateAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.saturation_effect,
+                new SaturationAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.sepia_effect,
+                new SepiaAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.shadow_effect,
+                new ShadowAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.sharpen_effect,
+                new SharpenAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.straighten_effect,
+                new StraightenAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.temperature_effect,
+                new ColorTemperatureAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.tint_effect,
+                new TintAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.vignette_effect,
+                new VignetteAction(filterStack, tools)));
+
+        effects.add(new Effect(R.id.warmify_effect,
+                new WarmifyAction(filterStack, tools)));
+
+        effectName = (TextView) tools.findViewById(R.id.action_effect_name);
+
+        // Disable hardware acceleration on this view to make alpha animations work for idle fading.
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+        setEnabled(false);
+    }
+
+    public void effectsOff(Runnable runnableOnEffectsOff) {
+        for (Effect effect : effects) {
+            if (effect.on) {
+                effect.turnOff(runnableOnEffectsOff);
+                return;
+            }
+        }
+        // Just execute the runnable right away if all effects are already off.
+        if (runnableOnEffectsOff != null) {
+            runnableOnEffectsOff.run();
+        }
+    }
+
+    public boolean hasEffectOn() {
+        for (Effect effect : effects) {
+            if (effect.on) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private class Effect implements FilterAction.FilterActionListener {
+
+        private final FilterAction action;
+        private final CharSequence name;
+        private final IconIndicator button;
+        private boolean on;
+        private Runnable runnableOnODone;
+
+        public Effect(int effectId, FilterAction action) {
+            this.action = action;
+
+            View view = findViewById(effectId);
+            name = ((TextView) view.findViewById(R.id.effect_label)).getText();
+            button = (IconIndicator) view.findViewById(R.id.effect_button);
+            button.setOnClickListener(new View.OnClickListener() {
+
+                @Override
+                public void onClick(View v) {
+                    if (isEnabled()) {
+                        if (on) {
+                            turnOff(null);
+                        } else {
+                            // Have other effects done turning off first and then turn on itself.
+                            effectsOff(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    turnOn();
+                                }
+                            });
+                        }
+                    }
+                }
+            });
+        }
+
+        private void turnOn() {
+            effectName.setText(name);
+            button.setMode("on");
+            on = true;
+            action.begin(this);
+        }
+
+        private void turnOff(Runnable runnableOnODone) {
+            this.runnableOnODone = runnableOnODone;
+            action.end();
+        }
+
+        @Override
+        public void onDone() {
+            if (on) {
+                effectName.setText("");
+                button.setMode("off");
+                on = false;
+
+                if (runnableOnODone != null) {
+                    runnableOnODone.run();
+                    runnableOnODone = null;
+                }
+            }
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/EffectsGroup.java b/samples/PhotoEditor/src/com/android/photoeditor/EffectsGroup.java
new file mode 100644
index 0000000..7c1f9af
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/EffectsGroup.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Groups effects in an accordion menu style that could either expand or fold effects.
+ */
+public class EffectsGroup extends LinearLayout {
+
+    private static final int ANIMATION_INTERVAL = 75;
+
+    private final Drawable downArrow;
+    private final Drawable rightArrow;
+
+    private boolean expandEffects;
+
+    public EffectsGroup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        downArrow = context.getResources().getDrawable(R.drawable.arrow_down);
+        rightArrow = context.getResources().getDrawable(R.drawable.arrow_right);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        if (expandEffects) {
+            requestRectangleOnScreen(new Rect(0, 0, 0, getHeight()));
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final ImageView arrow = (ImageView) findViewById(R.id.group_arrow);
+        final ViewGroup container = (ViewGroup) findViewById(R.id.grouped_effects);
+        final List<View> effects = new ArrayList<View>();
+        for (int i = 0; i < container.getChildCount(); i++) {
+            effects.add(container.getChildAt(i));
+        }
+
+        findViewById(R.id.group_header).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+
+                if (container.getVisibility() == VISIBLE) {
+                    expandEffects = false;
+
+                    int delay = 0;
+                    for (int i = effects.size() - 1; i >= 0; i--) {
+                        final View effect = effects.get(i);
+
+                        postDelayed(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                effect.setVisibility(GONE);
+                            }
+                        }, delay);
+                        delay += ANIMATION_INTERVAL;
+                    }
+
+                    postDelayed(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            container.setVisibility(GONE);
+                            arrow.setImageDrawable(rightArrow);
+                        }
+                    }, delay - ANIMATION_INTERVAL);
+                } else {
+                    expandEffects = true;
+
+                    arrow.setImageDrawable(downArrow);
+                    container.setVisibility(VISIBLE);
+
+                    int delay = 0;
+                    for (int i = 0; i < effects.size(); i++) {
+                        final View effect = effects.get(i);
+
+                        postDelayed(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                effect.setVisibility(VISIBLE);
+                            }
+                        }, delay);
+                        delay += ANIMATION_INTERVAL;
+                    }
+                }
+            }
+        });
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/FilterStack.java b/samples/PhotoEditor/src/com/android/photoeditor/FilterStack.java
new file mode 100644
index 0000000..9a9b8d7
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/FilterStack.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+import com.android.photoeditor.filters.Filter;
+
+import java.util.Stack;
+import java.util.Vector;
+
+/**
+ * A stack of filters to be applied onto a photo.
+ */
+public class FilterStack {
+
+    /**
+     * Listener of stack changes.
+     */
+    public interface StackListener {
+
+        void onStackChanged(boolean canUndo, boolean canRedo);
+    }
+
+    private static class OutputMessageObj {
+        PhotoOutputCallback callback;
+        Photo result;
+    }
+
+    private static final int COPY_SOURCE = 1;
+    private static final int COPY_RESULT = 2;
+    private static final int SET_SOURCE = 3;
+    private static final int CLEAR_SOURCE = 4;
+    private static final int CLEAR_STACKS = 5;
+    private static final int PUSH_FILTER = 6;
+    private static final int UNDO = 7;
+    private static final int REDO = 8;
+    private static final int TOP_FILTER_CHANGE = 9;
+    private static final int OUTPUT = 10;
+
+    private final Stack<Filter> appliedStack = new Stack<Filter>();
+    private final Stack<Filter> redoStack = new Stack<Filter>();
+    private final Vector<Message> pendingMessages = new Vector<Message>();
+    private final Handler mainHandler;
+    private final Handler workerHandler;
+
+    // Use two photo buffers as in and out in turns to apply filters in the stack.
+    private final Photo[] buffers = new Photo[2];
+
+    private Photo source;
+    private StackListener stackListener;
+
+    public FilterStack() {
+        HandlerThread workerThread = new HandlerThread("FilterStackWorker");
+        workerThread.start();
+
+        mainHandler = new Handler() {
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case OUTPUT:
+                        OutputMessageObj obj = (OutputMessageObj) msg.obj;
+                        obj.callback.onReady(obj.result);
+                        break;
+                }
+            }
+        };
+        workerHandler = new Handler(workerThread.getLooper()) {
+
+            private void output(PhotoOutputCallback callback, Photo target) {
+                // Copy target photo in rgb-565 format to update photo-view or save.
+                OutputMessageObj obj = new OutputMessageObj();
+                obj.callback = callback;
+                obj.result = (target != null) ? target.copy(Bitmap.Config.RGB_565) : null;
+                mainHandler.sendMessage(mainHandler.obtainMessage(OUTPUT, obj));
+            }
+
+            private void clearBuffers() {
+                pendingMessages.clear();
+                workerHandler.removeMessages(TOP_FILTER_CHANGE);
+                mainHandler.removeMessages(OUTPUT);
+                for (int i = 0; i < buffers.length; i++) {
+                    if (buffers[i] != null) {
+                        buffers[i].clear();
+                        buffers[i] = null;
+                    }
+                }
+            }
+
+            private void reallocateBuffer(int target) {
+                int other = target ^ 1;
+                buffers[target] = Photo.create(Bitmap.createBitmap(
+                        buffers[other].width(), buffers[other].height(), Bitmap.Config.ARGB_8888));
+            }
+
+            private void invalidate() {
+                // In/out buffers need redrawn by reloading source photo and re-applying filters.
+                clearBuffers();
+                buffers[0] = (source != null) ? source.copy(Bitmap.Config.ARGB_8888) : null;
+                if (buffers[0] != null) {
+                    reallocateBuffer(1);
+
+                    int out = 1;
+                    for (Filter filter : appliedStack) {
+                        runFilter(filter, out);
+                        out = out ^ 1;
+                    }
+                }
+            }
+
+            private void runFilter(Filter filter, int out) {
+                if ((buffers[0] != null) && (buffers[1] != null)) {
+                    int in = out ^ 1;
+                    filter.process(buffers[in], buffers[out]);
+                    if (!buffers[in].matchDimension(buffers[out])) {
+                        buffers[in].clear();
+                        reallocateBuffer(in);
+                    }
+                }
+            }
+
+            private int getOutBufferIndex() {
+                // buffers[0] and buffers[1] are swapped in turns as the in/out buffers for
+                // processing stacked filters. For example, the first filter reads buffer[0] and
+                // writes buffer[1]; the second filter then reads buffer[1] and writes buffer[0].
+                return appliedStack.size() % 2;
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case COPY_SOURCE:
+                        output((PhotoOutputCallback) msg.obj, source);
+                        break;
+
+                    case COPY_RESULT:
+                        output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
+                        break;
+
+                    case SET_SOURCE:
+                        source = (Photo) msg.obj;
+                        invalidate();
+                        break;
+
+                    case CLEAR_SOURCE:
+                        if (source != null) {
+                            source.clear();
+                            source = null;
+                        }
+                        clearBuffers();
+                        break;
+
+                    case CLEAR_STACKS:
+                        redoStack.clear();
+                        appliedStack.clear();
+                        break;
+
+                    case PUSH_FILTER:
+                        redoStack.clear();
+                        appliedStack.push((Filter) msg.obj);
+                        stackChanged();
+                        break;
+
+                    case UNDO:
+                        if (!appliedStack.empty()) {
+                            redoStack.push(appliedStack.pop());
+                            stackChanged();
+                            invalidate();
+                        }
+                        output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
+                        break;
+
+                    case REDO:
+                        if (!redoStack.empty()) {
+                            Filter filter = redoStack.pop();
+                            appliedStack.push(filter);
+                            stackChanged();
+                            runFilter(filter, getOutBufferIndex());
+                        }
+                        output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
+                        break;
+
+                    case TOP_FILTER_CHANGE:
+                        if (pendingMessages.remove(msg) && !appliedStack.empty()) {
+                            int out = getOutBufferIndex();
+                            runFilter(appliedStack.peek(), out);
+                            output((PhotoOutputCallback) msg.obj, buffers[out]);
+                        }
+                        break;
+                }
+            }
+        };
+    }
+
+    public void getSourceCopy(PhotoOutputCallback callback) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(COPY_SOURCE, callback));
+    }
+
+    public void getResultCopy(PhotoOutputCallback callback) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(COPY_RESULT, callback));
+    }
+
+    public void setPhotoSource(Photo source) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(SET_SOURCE, source));
+    }
+
+    public void clearPhotoSource() {
+        workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_SOURCE));
+    }
+
+    public void clearStacks() {
+        workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_STACKS));
+    }
+
+    public void pushFilter(Filter filter) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(PUSH_FILTER, filter));
+    }
+
+    public void undo(PhotoOutputCallback callback) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(UNDO, callback));
+    }
+
+    public void redo(PhotoOutputCallback callback) {
+        workerHandler.sendMessage(workerHandler.obtainMessage(REDO, callback));
+    }
+
+    public void topFilterChanged(PhotoOutputCallback callback) {
+        // Flush outdated top-filter messages before sending new ones.
+        Message msg = workerHandler.obtainMessage(TOP_FILTER_CHANGE, callback);
+        pendingMessages.clear();
+        pendingMessages.add(msg);
+        workerHandler.removeMessages(TOP_FILTER_CHANGE);
+        workerHandler.sendMessage(msg);
+    }
+
+    public synchronized void setStackListener(StackListener listener) {
+        stackListener = listener;
+    }
+
+    private synchronized void stackChanged() {
+        if (stackListener != null) {
+            stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty());
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/IconIndicator.java b/samples/PhotoEditor/src/com/android/photoeditor/IconIndicator.java
new file mode 100644
index 0000000..173e88e
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/IconIndicator.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * Represents image icons/buttons having various modes.
+ */
+public class IconIndicator extends ImageView {
+
+    private Drawable[] mIcons;
+    private CharSequence[] mModes;
+
+    public IconIndicator(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        TypedArray a = context
+            .obtainStyledAttributes(attrs, R.styleable.IconIndicator, defStyle, 0);
+        Drawable icons[] = loadIcons(context.getResources(), a.getResourceId(
+            R.styleable.IconIndicator_icons, 0));
+        CharSequence modes[] = a.getTextArray(R.styleable.IconIndicator_modes);
+        a.recycle();
+
+        setModesAndIcons(modes, icons);
+        setImageDrawable(mIcons.length > 0 ? mIcons[0] : null);
+    }
+
+    public IconIndicator(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    private Drawable[] loadIcons(Resources resources, int iconsId) {
+        TypedArray array = resources.obtainTypedArray(iconsId);
+        int n = array.length();
+        Drawable drawable[] = new Drawable[n];
+        for (int i = 0; i < n; ++i) {
+            int id = array.getResourceId(i, 0);
+            drawable[i] = id == 0 ? null : resources.getDrawable(id);
+        }
+        array.recycle();
+        return drawable;
+    }
+
+    private void setModesAndIcons(CharSequence[] modes, Drawable icons[]) {
+        if (modes.length != icons.length || icons.length == 0) {
+            throw new IllegalArgumentException();
+        }
+        mIcons = icons;
+        mModes = modes;
+    }
+
+    public void setMode(String mode) {
+        for (int i = 0, n = mModes.length; i < n; ++i) {
+            if (mModes[i].equals(mode)) {
+                setImageDrawable(mIcons[i]);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("unknown mode: " + mode);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/LoadScreennailTask.java b/samples/PhotoEditor/src/com/android/photoeditor/LoadScreennailTask.java
new file mode 100644
index 0000000..89ba0e2
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/LoadScreennailTask.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.widget.Toast;
+
+/**
+ * Asynchronous task for loading source photo screennail.
+ */
+public class LoadScreennailTask extends AsyncTask<Uri, Void, Bitmap> {
+
+    /**
+     * Callback for the completed asynchronous task.
+     */
+    public interface Callback {
+
+        void onComplete(Bitmap bitmap);
+    }
+
+    // TODO: Support 1280x960 once OOM is fixed.
+    private static final int SCREENNAIL_WIDTH = 1024;
+    private static final int SCREENNAIL_HEIGHT = 768;
+
+    private final Context context;
+    private final Callback callback;
+
+    public LoadScreennailTask(Context context, Callback callback) {
+        this.context = context;
+        this.callback = callback;
+    }
+
+    /**
+     * The task should be executed with one given source photo uri.
+     */
+    @Override
+    protected Bitmap doInBackground(Uri... params) {
+        if (params[0] == null) {
+            return null;
+        }
+        return new BitmapUtils(context).getBitmap(params[0], SCREENNAIL_WIDTH, SCREENNAIL_HEIGHT);
+    }
+
+    @Override
+    protected void onPostExecute(Bitmap bitmap) {
+        if (bitmap == null) {
+            Toast.makeText(context, R.string.loading_failure, Toast.LENGTH_SHORT).show();
+        }
+        callback.onComplete(bitmap);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/Photo.java b/samples/PhotoEditor/src/com/android/photoeditor/Photo.java
new file mode 100644
index 0000000..07898a9
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/Photo.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+
+/**
+ * Photo that is used for editing/display and should be synchronized for concurrent access.
+ */
+public class Photo {
+
+    private Bitmap bitmap;
+
+    /**
+     * Factory method to ensure every Photo instance holds a non-null bitmap.
+     */
+    public static Photo create(Bitmap bitmap) {
+        return (bitmap != null) ? new Photo(bitmap) : null;
+    }
+
+    private Photo(Bitmap bitmap) {
+        this.bitmap = bitmap;
+    }
+
+    public Bitmap bitmap() {
+        return bitmap;
+    }
+
+    public Photo copy(Bitmap.Config config) {
+        Bitmap copy = bitmap.copy(config, true);
+        return (copy != null) ? new Photo(copy) : null;
+    }
+
+    public boolean matchDimension(Photo photo) {
+        return ((photo.width() == width()) && (photo.height() == height()));
+    }
+
+    public int width() {
+        return bitmap.getWidth();
+    }
+
+    public int height() {
+        return bitmap.getHeight();
+    }
+
+    public void transform(Matrix matrix) {
+        // Copy immutable transformed photo; no-op if it fails to ensure bitmap isn't assigned null.
+        Bitmap transformed = BitmapUtils.createBitmap(bitmap, matrix).copy(
+                bitmap.getConfig(), true);
+        if (transformed != null) {
+            bitmap.recycle();
+            bitmap = transformed;
+        }
+    }
+
+    public void crop(int left, int top, int width, int height) {
+        // Copy immutable cropped photo; no-op if it fails to ensure bitmap isn't assigned null.
+        Bitmap cropped = Bitmap.createBitmap(bitmap, left, top, width, height).copy(
+                bitmap.getConfig(), true);
+        if (cropped != null) {
+            bitmap.recycle();
+            bitmap = cropped;
+        }
+    }
+
+    /**
+     * Recycles bitmaps; this instance should not be used after its clear() is called.
+     */
+    public void clear() {
+        bitmap.recycle();
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/PhotoEditor.java b/samples/PhotoEditor/src/com/android/photoeditor/PhotoEditor.java
new file mode 100644
index 0000000..928f523
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/PhotoEditor.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.android.photoeditor.filters.ImageUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * Main activity of the photo editor.
+ */
+public class PhotoEditor extends Activity {
+
+    private Toolbar toolbar;
+    private View backButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+	{
+          // HACK: create faked view in order to read bitcode in resource
+                View view = new View(getApplication());
+                byte[] pgm;
+                int pgmLength;
+
+            // read bitcode in res
+            InputStream is = view.getResources().openRawResource(R.raw.libjni_photoeditor_portable);
+            try {
+               try {
+                  pgm = new byte[1024];
+                  pgmLength = 0;
+
+                  while(true) {
+                     int bytesLeft = pgm.length - pgmLength;
+                     if (bytesLeft == 0) {
+                         byte[] buf2 = new byte[pgm.length * 2];
+                         System.arraycopy(pgm, 0, buf2, 0, pgm.length);
+                         pgm = buf2;
+                         bytesLeft = pgm.length - pgmLength;
+                     }
+                     int bytesRead = is.read(pgm, pgmLength, bytesLeft);
+                     if (bytesRead <= 0) {
+                        break;
+                     }
+                     pgmLength += bytesRead;
+    	          }
+                  ImageUtils.init(pgm, pgmLength);
+               } finally {
+                  is.close();
+               }
+            } catch(IOException e) {
+               throw new Resources.NotFoundException();
+            }
+	}
+
+        toolbar = (Toolbar) findViewById(R.id.toolbar);
+        toolbar.initialize();
+
+        final EffectsBar effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
+        final View actionBar = findViewById(R.id.action_bar);
+        final View quickviewOn = findViewById(R.id.quickview_on_button);
+        backButton = findViewById(R.id.action_bar_back);
+        backButton.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (actionBar.isEnabled()) {
+                    if (quickviewOn.getVisibility() == View.VISIBLE) {
+                        quickviewOn.performClick();
+                    } else if (effectsBar.hasEffectOn()) {
+                        effectsBar.effectsOff(null);
+                    } else {
+                        tryRun(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                finish();
+                            }
+                        });
+                    }
+                }
+            }
+        });
+
+        Intent intent = getIntent();
+        if (Intent.ACTION_EDIT.equalsIgnoreCase(intent.getAction()) && (intent.getData() != null)) {
+            toolbar.openPhoto(intent.getData());
+        }
+    }
+
+    private void tryRun(final Runnable runnable) {
+        if (findViewById(R.id.save_button).isEnabled()) {
+            // Pop-up a dialog before executing the runnable to save unsaved photo.
+            AlertDialog.Builder builder = new AlertDialog.Builder(this)
+                    .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            toolbar.savePhoto(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    runnable.run();
+                                }
+                            });
+                        }
+                    })
+                    .setNeutralButton(R.string.no, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            runnable.run();
+                        }
+                    })
+                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            // no-op
+                        }
+                    });
+            builder.setMessage(R.string.save_photo).show();
+            return;
+        }
+
+        runnable.run();
+    }
+
+    @Override
+    public void onBackPressed() {
+        backButton.performClick();
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/PhotoOutputCallback.java b/samples/PhotoEditor/src/com/android/photoeditor/PhotoOutputCallback.java
new file mode 100644
index 0000000..75651f3
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/PhotoOutputCallback.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+/**
+ * Callback of photo output that will only be called in UI thread.
+ */
+public interface PhotoOutputCallback {
+
+    void onReady(Photo photo);
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/PhotoView.java b/samples/PhotoEditor/src/com/android/photoeditor/PhotoView.java
new file mode 100644
index 0000000..1853d7c
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/PhotoView.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.Animation;
+
+import com.android.photoeditor.animation.AnimationPair;
+
+/**
+ * Displays photo in the view. All its methods should be called from UI thread.
+ */
+public class PhotoView extends View {
+
+    private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+    private final Matrix displayMatrix = new Matrix();
+    private Photo photo;
+    private RectF clipBounds;
+    private AnimationPair transitions;
+
+    public PhotoView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (photo != null) {
+            canvas.save();
+            if (clipBounds != null) {
+                canvas.clipRect(clipBounds);
+            }
+            canvas.concat(displayMatrix);
+            canvas.drawBitmap(photo.bitmap(), 0, 0, paint);
+            canvas.restore();
+        }
+    }
+
+    /**
+     * Maps x and y to a percentage position relative to displayed photo.
+     */
+    public PointF mapPhotoPoint(float x, float y) {
+        if ((photo == null) || (photo.width() == 0) || (photo.height() == 0)) {
+            return new PointF();
+        }
+        float[] point = new float[] {x, y};
+        Matrix matrix = new Matrix();
+        displayMatrix.invert(matrix);
+        matrix.mapPoints(point);
+        return new PointF(point[0] / photo.width(), point[1] / photo.height());
+    }
+
+    public void mapPhotoPath(Path src, Path dst) {
+        // TODO: Use percentages representing paths for saving photo larger than previewed photo.
+        Matrix matrix = new Matrix();
+        displayMatrix.invert(matrix);
+        src.transform(matrix, dst);
+    }
+
+    public RectF getPhotoDisplayBounds() {
+        RectF bounds = getPhotoBounds();
+        displayMatrix.mapRect(bounds);
+        return bounds;
+    }
+
+    public RectF getPhotoBounds() {
+        return (photo != null) ? new RectF(0, 0, photo.width(), photo.height()) : new RectF();
+    }
+
+    /**
+     * Transforms display by replacing the display matrix of photo-view with the given matrix.
+     */
+    public void transformDisplay(Matrix matrix) {
+        RectF bounds = getPhotoBounds();
+        matrix.mapRect(bounds);
+        displayMatrix.set(matrix);
+        RectUtils.postCenterMatrix(bounds, this, displayMatrix);
+        invalidate();
+    }
+
+    public void clipPhoto(RectF bounds) {
+        clipBounds = bounds;
+        invalidate();
+    }
+
+    /**
+     * Updates the photo with animations (if any) and also updates photo display-matrix.
+     */
+    public void update(final Photo photo) {
+        if (transitions == null) {
+            setPhoto(photo);
+            invalidate();
+        } else if (getAnimation() != null) {
+            // Clear old running transitions.
+            clearTransitionAnimations();
+            setPhoto(photo);
+            invalidate();
+        } else {
+            // TODO: Use AnimationSet to chain two animations.
+            transitions.first().setAnimationListener(new Animation.AnimationListener() {
+
+                @Override
+                public void onAnimationStart(Animation animation) {
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {
+                }
+
+                @Override
+                public void onAnimationEnd(final Animation animation) {
+                    post(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            if ((transitions != null) && (animation == transitions.first())) {
+                                startAnimation(transitions.second());
+                            }
+                        }
+                    });
+                }
+            });
+            transitions.second().setAnimationListener(new Animation.AnimationListener() {
+
+                @Override
+                public void onAnimationStart(Animation animation) {
+                    setPhoto(photo);
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {
+                }
+
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    post(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            clearTransitionAnimations();
+                        }
+                    });
+                }
+            });
+            startAnimation(transitions.first());
+        }
+    }
+
+    private void setPhoto(Photo photo) {
+        if (this.photo != null) {
+            this.photo.clear();
+            this.photo = null;
+        }
+        this.photo = photo;
+
+        // Scale-down (if necessary) and center the photo for display.
+        displayMatrix.reset();
+        RectF bounds = getPhotoBounds();
+        float scale = RectUtils.getDisplayScale(bounds, this);
+        displayMatrix.setScale(scale, scale);
+        displayMatrix.mapRect(bounds);
+        RectUtils.postCenterMatrix(bounds, this, displayMatrix);
+    }
+
+    private void clearTransitionAnimations() {
+        if (transitions != null) {
+            transitions.first().setAnimationListener(null);
+            transitions.second().setAnimationListener(null);
+            transitions = null;
+            clearAnimation();
+        }
+    }
+
+    /**
+     * Sets transition animations in the next update() to transit out the current bitmap and
+     * transit in the new replacing one. Transition animations will be cleared once done.
+     */
+    public void setTransitionAnimations(AnimationPair transitions) {
+        clearTransitionAnimations();
+        this.transitions = transitions;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/RectUtils.java b/samples/PhotoEditor/src/com/android/photoeditor/RectUtils.java
new file mode 100644
index 0000000..e3945ca
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/RectUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.View;
+
+/**
+ * Utils for rectangles/bounds related calculations.
+ */
+public class RectUtils {
+
+    private static final float MATH_PI = (float) Math.PI;
+    private static final float DEGREES_TO_RADIAN = MATH_PI / 180.0f;
+
+    /**
+     * Gets straighten matrix for the given bounds and degrees.
+     */
+    public static void getStraightenMatrix(RectF bounds, float degrees, Matrix matrix) {
+        matrix.reset();
+        if ((degrees != 0) && !bounds.isEmpty()) {
+            float w = bounds.width() / 2;
+            float h = bounds.height() / 2;
+            float adjustAngle;
+            if ((degrees < 0 && w > h) || (degrees > 0 && w <= h)) {
+                // The top left point is the boundary.
+                adjustAngle = (float) Math.atan(h / -w) + MATH_PI + degrees * DEGREES_TO_RADIAN;
+            } else {
+                // The top right point is the boundary.
+                adjustAngle = (float) Math.atan(h / w) - MATH_PI + degrees * DEGREES_TO_RADIAN;
+            }
+            float radius = (float) Math.hypot(w, h);
+            float scaleX = (float) Math.abs(radius * Math.cos(adjustAngle)) / w;
+            float scaleY = (float) Math.abs(radius * Math.sin(adjustAngle)) / h;
+            float scale = Math.max(scaleX, scaleY);
+
+            postRotateMatrix(degrees, new RectF(bounds), matrix);
+            matrix.postScale(scale, scale);
+        }
+    }
+
+    /**
+     * Post rotates the matrix and bounds for the given bounds and degrees.
+     */
+    public static void postRotateMatrix(float degrees, RectF bounds, Matrix matrix) {
+        matrix.postRotate(degrees);
+        matrix.mapRect(bounds);
+        matrix.postTranslate(-bounds.left, -bounds.top);
+    }
+
+    /**
+     * Post translates the matrix to center the given bounds inside the view.
+     */
+    public static void postCenterMatrix(RectF contentBounds, View view, Matrix matrix) {
+        matrix.postTranslate((view.getWidth() - contentBounds.width()) / 2,
+                (view.getHeight() - contentBounds.height()) / 2);
+    }
+
+    /**
+     * Gets the proper scale value that scales down the content and keeps its aspect ratio to
+     * display inside the view.
+     */
+    public static float getDisplayScale(RectF contentBounds, View view) {
+        if (contentBounds.isEmpty()) {
+            return 1;
+        }
+
+        float scale = Math.min(view.getWidth() / contentBounds.width(),
+                view.getHeight() / contentBounds.height());
+        // Avoid scaling up the content.
+        return Math.min(scale, 1);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/SaveCopyTask.java b/samples/PhotoEditor/src/com/android/photoeditor/SaveCopyTask.java
new file mode 100644
index 0000000..3706600
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/SaveCopyTask.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap.CompressFormat;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.widget.Toast;
+
+import java.io.File;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+
+/**
+ * Asynchronous task for loading source photo in target dimensions and saving edits as a new copy.
+ */
+public class SaveCopyTask extends AsyncTask<Photo, Void, Uri> {
+
+    /**
+     * Callback for the completed asynchronous task.
+     */
+    public interface Callback {
+
+        void onComplete(Uri uri);
+    }
+
+    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
+    private static final int INDEX_DATE_TAKEN = 0;
+    private static final int INDEX_LATITUDE = 1;
+    private static final int INDEX_LONGITUDE = 2;
+
+    private static final String[] IMAGE_PROJECTION = new String[] {
+        ImageColumns.DATE_TAKEN,
+        ImageColumns.LATITUDE,
+        ImageColumns.LONGITUDE,
+    };
+
+    private final Context context;
+    private final Uri sourceUri;
+    private final Callback callback;
+    private final String saveFileName;
+
+    public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
+        this.context = context;
+        this.sourceUri = sourceUri;
+        this.callback = callback;
+
+        saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
+                new Date(System.currentTimeMillis()));
+    }
+
+    /**
+     * The task should be executed with one given photo to be saved.
+     */
+    @Override
+    protected Uri doInBackground(Photo... params) {
+        // TODO: Support larger dimensions for photo saving.
+        if (params[0] == null) {
+            return null;
+        }
+        Photo photo = params[0];
+        File file = save(photo);
+        Uri uri = (file != null) ? insertContent(file) : null;
+        photo.clear();
+        return uri;
+    }
+
+    @Override
+    protected void onPostExecute(Uri result) {
+        String message = (result == null) ? context.getString(R.string.saving_failure)
+                : context.getString(R.string.photo_saved, saveFileName);
+        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+
+        callback.onComplete(result);
+    }
+
+    private File save(Photo photo) {
+        String directory = Environment.getExternalStorageDirectory().toString() + "/"
+                + context.getString(R.string.edited_photo_bucket_name);
+
+        return new BitmapUtils(context).saveBitmap(
+                photo.bitmap(), directory, saveFileName, CompressFormat.JPEG);
+    }
+
+    /**
+     * Insert the content (saved file) with proper source photo properties.
+     */
+    private Uri insertContent(File file) {
+        long now = System.currentTimeMillis() / 1000;
+        long dateTaken = now;
+        double latitude = 0f;
+        double longitude = 0f;
+
+        ContentResolver contentResolver = context.getContentResolver();
+        Cursor cursor = contentResolver.query(
+                sourceUri, IMAGE_PROJECTION, null, null, null);
+        if ((cursor != null) && cursor.moveToNext()) {
+            dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
+            latitude = cursor.getDouble(INDEX_LATITUDE);
+            longitude = cursor.getDouble(INDEX_LONGITUDE);
+        }
+
+        ContentValues values = new ContentValues();
+        values.put(Images.Media.TITLE, saveFileName);
+        values.put(Images.Media.DISPLAY_NAME, saveFileName);
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATE_TAKEN, dateTaken);
+        values.put(Images.Media.DATE_MODIFIED, now);
+        values.put(Images.Media.DATE_ADDED, now);
+        values.put(Images.Media.ORIENTATION, 0);
+        values.put(Images.Media.DATA, file.getAbsolutePath());
+        values.put(Images.Media.SIZE, file.length());
+
+        // TODO: Change || to && after the default location issue is fixed.
+        if ((latitude != 0f) || (longitude != 0f)) {
+            values.put(Images.Media.LATITUDE, latitude);
+            values.put(Images.Media.LONGITUDE, longitude);
+        }
+        return contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/SpinnerProgressDialog.java b/samples/PhotoEditor/src/com/android/photoeditor/SpinnerProgressDialog.java
new file mode 100644
index 0000000..4e29298
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/SpinnerProgressDialog.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.app.Dialog;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ProgressBar;
+
+/**
+ * Spinner model progress dialog that disables all tools for user interaction after it shows up and
+ * and re-enables them after it dismisses.
+ */
+public class SpinnerProgressDialog extends Dialog {
+
+    private final ViewGroup tools;
+
+    public static SpinnerProgressDialog show(ViewGroup tools) {
+        SpinnerProgressDialog dialog = new SpinnerProgressDialog(tools);
+        dialog.show();
+        return dialog;
+    }
+
+    private SpinnerProgressDialog(ViewGroup tools) {
+        super(tools.getContext(), R.style.SpinnerProgressDialog);
+
+        addContentView(new ProgressBar(tools.getContext()), new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        this.tools = tools;
+        enableTools(false);
+    }
+
+    @Override
+    public void dismiss() {
+        super.dismiss();
+
+        enableTools(true);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // Pass touch events to tools for killing idle even when the progress dialog is shown.
+        return tools.onInterceptTouchEvent(event);
+    }
+
+    private void enableTools(boolean enabled) {
+        for (int i = 0; i < tools.getChildCount(); i++) {
+            tools.getChildAt(i).setEnabled(enabled);
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/Toolbar.java b/samples/PhotoEditor/src/com/android/photoeditor/Toolbar.java
new file mode 100644
index 0000000..106cf75
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/Toolbar.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.RelativeLayout;
+
+import com.android.photoeditor.animation.FadeAnimation;
+import com.android.photoeditor.animation.Rotate3DAnimation;
+import com.android.photoeditor.animation.Rotate3DAnimation.Rotate;
+
+/**
+ * Toolbar that contains all tools and handles all operations for editing photo.
+ */
+public class Toolbar extends RelativeLayout {
+
+    private static final int UNDO_REDO_ANIMATION_DURATION = 100;
+    private static final int QUICKVIEW_ANIMATION_DURATION = 150;
+
+    private final FilterStack filterStack = new FilterStack();
+    private ToolbarLayoutHandler layoutHandler;
+    private ToolbarIdleHandler idleHandler;
+    private EffectsBar effectsBar;
+    private ActionBar actionBar;
+    private PhotoView photoView;
+    private SpinnerProgressDialog progressDialog;
+    private Uri sourceUri;
+
+    public Toolbar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        idleHandler.killIdle();
+        return false;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        layoutHandler.extraLayout(l, t, r, b);
+    }
+
+    public void initialize() {
+        photoView = (PhotoView) findViewById(R.id.photo_view);
+        initializeEffectsBar();
+        initializeActionBar();
+        layoutHandler = new ToolbarLayoutHandler(this);
+        idleHandler = new ToolbarIdleHandler(this);
+        idleHandler.killIdle();
+    }
+
+    private void initializeEffectsBar() {
+        effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
+        effectsBar.initialize(filterStack, photoView, this);
+    }
+
+    private void initializeActionBar() {
+        final PhotoOutputCallback callback = new PhotoOutputCallback() {
+
+            @Override
+            public void onReady(Photo photo) {
+                photoView.setTransitionAnimations(
+                        FadeAnimation.getFadeOutInAnimations(UNDO_REDO_ANIMATION_DURATION));
+                photoView.update(photo);
+                progressDialog.dismiss();
+            }
+        };
+
+        actionBar = (ActionBar) findViewById(R.id.action_bar);
+        actionBar.initialize(new ActionBar.ActionBarListener() {
+
+            @Override
+            public void onUndo() {
+                effectsBar.effectsOff(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        progressDialog = SpinnerProgressDialog.show(Toolbar.this);
+                        filterStack.undo(callback);
+                    }
+                });
+            }
+
+            @Override
+            public void onRedo() {
+                effectsBar.effectsOff(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        progressDialog = SpinnerProgressDialog.show(Toolbar.this);
+                        filterStack.redo(callback);
+                    }
+                });
+            }
+
+            @Override
+            public void onQuickview(final boolean on) {
+                final PhotoOutputCallback callback = new PhotoOutputCallback() {
+
+                    @Override
+                    public void onReady(Photo photo) {
+                        photoView.setTransitionAnimations(Rotate3DAnimation.getFlipAnimations(
+                                on ? Rotate.RIGHT : Rotate.LEFT, QUICKVIEW_ANIMATION_DURATION));
+                        photoView.update(photo);
+                    }
+                };
+
+                if (on) {
+                    effectsBar.effectsOff(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            effectsBar.setVisibility(INVISIBLE);
+                            filterStack.getSourceCopy(callback);
+                        }
+                    });
+                } else {
+                    effectsBar.setVisibility(VISIBLE);
+                    filterStack.getResultCopy(callback);
+                }
+            }
+
+            @Override
+            public void onSave() {
+                effectsBar.effectsOff(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        savePhoto(null);
+                    }
+                });
+            }
+        });
+    }
+
+    public void openPhoto(Uri uri) {
+        sourceUri = uri;
+        filterStack.setStackListener(actionBar);
+
+        // clearPhotoSource() should be called before loading a new source photo to avoid OOM.
+        progressDialog = SpinnerProgressDialog.show(this);
+        filterStack.clearPhotoSource();
+        new LoadScreennailTask(getContext(), new LoadScreennailTask.Callback() {
+
+            @Override
+            public void onComplete(Bitmap bitmap) {
+                filterStack.setPhotoSource(Photo.create(bitmap));
+                filterStack.getResultCopy(new PhotoOutputCallback() {
+
+                    @Override
+                    public void onReady(Photo photo) {
+                        photoView.update(photo);
+                        progressDialog.dismiss();
+                        effectsBar.setEnabled(photo != null);
+                    }
+                });
+            }
+        }).execute(sourceUri);
+    }
+
+    /**
+     * Saves photo and executes runnable (if provided) after saving done.
+     */
+    public void savePhoto(final Runnable runnable) {
+        progressDialog = SpinnerProgressDialog.show(this);
+        filterStack.getResultCopy(new PhotoOutputCallback() {
+
+            @Override
+            public void onReady(Photo photo) {
+                new SaveCopyTask(getContext(), sourceUri, new SaveCopyTask.Callback() {
+
+                    @Override
+                    public void onComplete(Uri uri) {
+                        // TODO: Handle saving failure.
+                        progressDialog.dismiss();
+                        actionBar.disableSave();
+                        if (runnable != null) {
+                            runnable.run();
+                        }
+                    }
+                }).execute(photo);
+            }
+        });
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/ToolbarIdleHandler.java b/samples/PhotoEditor/src/com/android/photoeditor/ToolbarIdleHandler.java
new file mode 100644
index 0000000..07b8f50
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/ToolbarIdleHandler.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handler that controls idle/awake behaviors of toolbar's child views.
+ */
+class ToolbarIdleHandler {
+
+    private static final String IDLE_VIEW_TAG = "fadeOnIdle";
+    private static final int MAKE_IDLE = 1;
+    private static final int TIMEOUT_IDLE = 8000;
+
+    private final List<View> childViews = new ArrayList<View>();
+    private final Handler mainHandler;
+    private final Animation fadeIn;
+    private final Animation fadeOut;
+    private boolean idle;
+
+    /**
+     * Constructor should only be invoked after toolbar has done inflation and added all its child
+     * views; then its child views could be found by findViewById calls.
+     */
+    public ToolbarIdleHandler(ViewGroup toolbar) {
+        mainHandler = new Handler() {
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MAKE_IDLE:
+                        if (!idle) {
+                            idle = true;
+                            for (View view : childViews) {
+                                makeIdle(view);
+                            }
+                        }
+                        break;
+                }
+            }
+        };
+
+        fadeIn = AnimationUtils.loadAnimation(toolbar.getContext(), R.anim.fade_in);
+        fadeOut = AnimationUtils.loadAnimation(toolbar.getContext(), R.anim.fade_out);
+
+        for (int i = 0; i < toolbar.getChildCount(); i++) {
+            View view = toolbar.getChildAt(i);
+            String tag = (String) view.getTag();
+            if ((tag != null) && tag.equals(IDLE_VIEW_TAG)) {
+                childViews.add(view);
+            }
+        }
+        // Alpha animations don't work well on scroll-view; apply them on container linear-layout.
+        childViews.add(toolbar.findViewById(R.id.effects_container));
+    }
+
+    public void killIdle() {
+        mainHandler.removeMessages(MAKE_IDLE);
+        if (idle) {
+            idle = false;
+            for (View view : childViews) {
+                makeAwake(view);
+            }
+        }
+        mainHandler.sendEmptyMessageDelayed(MAKE_IDLE, TIMEOUT_IDLE);
+    }
+
+    private void makeAwake(View view) {
+        if (view.getVisibility() == View.VISIBLE) {
+            view.startAnimation(fadeIn);
+        }
+    }
+
+    private void makeIdle(View view) {
+        if (view.getVisibility() == View.VISIBLE) {
+            view.startAnimation(fadeOut);
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/ToolbarLayoutHandler.java b/samples/PhotoEditor/src/com/android/photoeditor/ToolbarLayoutHandler.java
new file mode 100644
index 0000000..93253d7
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/ToolbarLayoutHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor;
+
+import android.view.View;
+
+/**
+ * Handler that adjusts layouts of toolbar's child views whose positions need being calculated
+ * according to the current screen dimensions.
+ */
+class ToolbarLayoutHandler {
+
+    private final View tools;
+
+    /**
+     * Constructor should only be invoked after toolbar has done inflation and added all its child
+     * views; then its child views could be found by findViewById calls.
+     */
+    public ToolbarLayoutHandler(View toolbar) {
+        this.tools = toolbar;
+    }
+
+    /**
+     * Layouts child tool views' positions that need being updated when toolbar is being layout.
+     */
+    public void extraLayout(int left, int top, int right, int bottom) {
+        // Wheels need being centered vertically.
+        int height = bottom - top;
+
+        View scaleWheel = tools.findViewById(R.id.scale_wheel);
+        scaleWheel.offsetTopAndBottom((height - scaleWheel.getHeight()) / 2);
+
+        View colorWheel = tools.findViewById(R.id.color_wheel);
+        colorWheel.offsetTopAndBottom((height - colorWheel.getHeight()) / 2);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/AutoFixAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/AutoFixAction.java
new file mode 100644
index 0000000..0c9f174
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/AutoFixAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.AutoFixFilter;
+
+/**
+ * An action handling auto-fix effect.
+ */
+public class AutoFixAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public AutoFixAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final AutoFixFilter filter = new AutoFixFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorPath.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorPath.java
new file mode 100644
index 0000000..1aedfb6
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorPath.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+
+/**
+ * Colored path that could be painted on canvas.
+ */
+public class ColorPath {
+
+    private final int color;
+    private final Path path;
+
+    public ColorPath(int color, Path path) {
+        this.color = Color.argb(192, Color.red(color), Color.green(color), Color.blue(color));
+        this.path = path;
+    }
+
+    public Path path() {
+        return path;
+    }
+
+    public void draw(Canvas canvas, Paint paint) {
+        paint.setColor(color);
+        canvas.drawPath(path, paint);
+    }
+
+    /**
+     * Creates a paint to draw color paths.
+     */
+    public static Paint createPaint() {
+        Paint paint = new Paint(Paint.DITHER_FLAG | Paint.ANTI_ALIAS_FLAG);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeJoin(Paint.Join.ROUND);
+        paint.setStrokeCap(Paint.Cap.ROUND);
+        paint.setStrokeWidth(12);
+        return paint;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorTemperatureAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorTemperatureAction.java
new file mode 100644
index 0000000..db2850f
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorTemperatureAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.ColorTemperatureFilter;
+
+/**
+ * An action handling color temperature effect.
+ */
+public class ColorTemperatureAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public ColorTemperatureAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final ColorTemperatureFilter filter = new ColorTemperatureFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setColorTemperature(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorWheel.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorWheel.java
new file mode 100644
index 0000000..7c7e715
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/ColorWheel.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+
+import com.android.photoeditor.R;
+
+/**
+ * Wheel that has a draggable thumb to set and get the predefined color set.
+ */
+class ColorWheel extends View {
+
+    /**
+     * Listens to color changes.
+     */
+    public interface OnColorChangeListener {
+
+        void onColorChanged(int color, boolean fromUser);
+    }
+
+    private static final float MATH_PI = (float) Math.PI;
+    private static final float MATH_HALF_PI = MATH_PI / 2;
+
+    // All angles used in this object are defined between PI and -PI.
+    private static final float ANGLE_SPANNED = MATH_PI * 4 / 3;
+    private static final float ANGLE_BEGIN = ANGLE_SPANNED / 2.0f;
+    private static final float DEGREES_BEGIN = 360 - (float) Math.toDegrees(ANGLE_BEGIN);
+    private static final float STROKE_WIDTH = 3.0f;
+
+    private static final float THUMB_RADIUS_RATIO = 0.363f;
+    private static final float INNER_RADIUS_RATIO = 0.173f;
+
+    private static final int PADDING = 4;
+    private static final int COLOR_METER_THICKNESS = 18;
+
+    private final Drawable thumb;
+    private final Paint fillPaint;
+    private final Paint strokePaint;
+    private final int thumbSize;
+    private final int borderColor;
+    private final int[] colorsDefined;
+    private final float radiantInterval;
+    private Bitmap background;
+    private int thumbRadius;
+    private int innerRadius;
+    private int centerXY;
+    private int colorIndex;
+    private float angle;
+    private boolean dragThumb;
+    private OnColorChangeListener listener;
+
+    public ColorWheel(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources resources = context.getResources();
+        thumbSize = (int) resources.getDimension(R.dimen.wheel_thumb_size);
+
+        // Set the number of total colors and compute the radiant interval between colors.
+        TypedArray colors = resources.obtainTypedArray(R.array.color_picker_wheel_colors);
+        colorsDefined = new int[colors.length()];
+        for (int c = 0; c < colors.length(); c++) {
+            colorsDefined[c] = colors.getColor(c, 0x000000);
+        }
+        colors.recycle();
+
+        radiantInterval = ANGLE_SPANNED / colorsDefined.length;
+
+        thumb = resources.getDrawable(R.drawable.wheel_knot_selector);
+        borderColor = resources.getColor(R.color.color_picker_border_color);
+
+        fillPaint = new Paint();
+        fillPaint.setAntiAlias(true);
+        fillPaint.setStyle(Paint.Style.FILL);
+        strokePaint = new Paint();
+        strokePaint.setAntiAlias(true);
+        strokePaint.setStrokeWidth(STROKE_WIDTH);
+        strokePaint.setStyle(Paint.Style.STROKE);
+    }
+
+    public void setColorIndex(int colorIndex) {
+        if (updateColorIndex(colorIndex, false)) {
+            updateThumbPositionByColorIndex();
+        }
+    }
+
+    public int getColor() {
+        return colorsDefined[colorIndex];
+    }
+
+    public void setOnColorChangeListener(OnColorChangeListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+
+        startAnimation(AnimationUtils.loadAnimation(getContext(),
+                (visibility == VISIBLE) ? R.anim.wheel_show : R.anim.wheel_hide));
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        int wheelSize = Math.min(w, h) - PADDING;
+        thumbRadius = (int) (wheelSize * THUMB_RADIUS_RATIO);
+        innerRadius = (int) (wheelSize * INNER_RADIUS_RATIO);
+
+        // The wheel would be centered at (centerXY, centerXY) and have outer-radius centerXY.
+        centerXY = wheelSize / 2;
+        updateThumbPositionByColorIndex();
+    }
+
+    private Bitmap prepareBackground() {
+        int diameter = centerXY * 2;
+        Bitmap bitmap = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // the colors to be selected.
+        float radiantDegrees = (float) Math.toDegrees(radiantInterval);
+        RectF drawBound = new RectF(0, 0, diameter, diameter);
+        for (int c = 0; c < colorsDefined.length; c++) {
+            fillPaint.setColor(colorsDefined[c]);
+            canvas.drawArc(drawBound, DEGREES_BEGIN + radiantDegrees * c,
+                    radiantDegrees, true, fillPaint);
+        }
+
+        // clear the inner area.
+        fillPaint.setColor(Color.BLACK);
+        fillPaint.setAlpha(160);
+        canvas.drawCircle(centerXY, centerXY, centerXY - COLOR_METER_THICKNESS, fillPaint);
+
+        // the border for the inner ball
+        fillPaint.setColor(borderColor);
+        canvas.drawCircle(centerXY, centerXY, innerRadius + STROKE_WIDTH, fillPaint);
+
+        return bitmap;
+    }
+
+    private void drawBackground(Canvas canvas) {
+        if (background == null) {
+            background = prepareBackground();
+        }
+        canvas.drawBitmap(background, 0, 0, fillPaint);
+    }
+
+    private void drawHighlighter(Canvas canvas) {
+        strokePaint.setColor(borderColor);
+        int diameter = centerXY * 2;
+        RectF drawBound = new RectF(0, 0, diameter, diameter);
+        float radiantDegrees = (float) Math.toDegrees(radiantInterval);
+        float startAngle = DEGREES_BEGIN + radiantDegrees * colorIndex;
+        canvas.drawArc(drawBound, startAngle, radiantDegrees, false, strokePaint);
+        drawBound.inset(COLOR_METER_THICKNESS, COLOR_METER_THICKNESS);
+        canvas.drawArc(drawBound, startAngle, radiantDegrees, false, strokePaint);
+
+        float lineAngle = ANGLE_BEGIN - radiantInterval * colorIndex;
+        float cosAngle = (float) Math.cos(lineAngle);
+        float sinAngle = (float) Math.sin(lineAngle);
+        int innerRadius = centerXY - COLOR_METER_THICKNESS;
+        canvas.drawLine(centerXY + centerXY * cosAngle, centerXY - centerXY * sinAngle,
+                centerXY + innerRadius * cosAngle,
+                centerXY - innerRadius * sinAngle, strokePaint);
+
+        lineAngle -= radiantInterval;
+        cosAngle = (float) Math.cos(lineAngle);
+        sinAngle = (float) Math.sin(lineAngle);
+        canvas.drawLine(centerXY + centerXY * cosAngle, centerXY - centerXY * sinAngle,
+                centerXY + innerRadius * cosAngle,
+                centerXY - innerRadius * sinAngle, strokePaint);
+    }
+
+    private void drawInnerCircle(Canvas canvas) {
+        fillPaint.setColor(colorsDefined[colorIndex]);
+        canvas.drawCircle(centerXY, centerXY, innerRadius, fillPaint);
+    }
+
+    private void drawThumb(Canvas canvas) {
+        int thumbX = (int) (thumbRadius * Math.cos(angle) + centerXY);
+        int thumbY = (int) (centerXY - thumbRadius * Math.sin(angle));
+        int halfSize = thumbSize / 2;
+        thumb.setBounds(thumbX - halfSize, thumbY - halfSize, thumbX + halfSize, thumbY + halfSize);
+        thumb.draw(canvas);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        drawBackground(canvas);
+        drawInnerCircle(canvas);
+        drawHighlighter(canvas);
+        drawThumb(canvas);
+    }
+
+    private boolean updateAngle(float x, float y) {
+        float angle;
+        if (x == 0) {
+            if (y >= 0) {
+                angle = MATH_HALF_PI;
+            } else {
+                angle = -MATH_HALF_PI;
+            }
+        } else {
+            angle = (float) Math.atan((double) y / x);
+        }
+
+        if (angle >= 0 && x < 0) {
+            angle = angle - MATH_PI;
+        } else if (angle < 0 && x < 0) {
+            angle = MATH_PI + angle;
+        }
+
+        if (angle > ANGLE_BEGIN || angle <= ANGLE_BEGIN - ANGLE_SPANNED) {
+            return false;
+        }
+
+        this.angle = angle;
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        super.onTouchEvent(ev);
+
+        if (isEnabled()) {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    updateThumbState(
+                            isHittingThumbArea(ev.getX() - centerXY, centerXY - ev.getY()));
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    final float x = ev.getX() - centerXY;
+                    final float y = centerXY - ev.getY();
+                    if (!dragThumb && !updateThumbState(isHittingThumbArea(x, y))) {
+                        // The thumb wasn't dragged and isn't being dragged, either.
+                        break;
+                    }
+
+                    if (updateAngle(x, y)) {
+                        int index = (int) ((ANGLE_BEGIN - angle) / radiantInterval);
+                        if (updateColorIndex(index, true)) {
+                            updateThumbPositionByColorIndex();
+                        }
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    updateThumbState(false);
+                    break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if the user is hitting the correct thumb area.
+     */
+    private boolean isHittingThumbArea(float x, float y) {
+        final float radius = (float) Math.sqrt((x * x) + (y * y));
+        return (radius > innerRadius) && (radius < centerXY);
+    }
+
+
+    private boolean updateColorIndex(int index, boolean fromUser) {
+        if (index < 0 || index >= colorsDefined.length) {
+            return false;
+        }
+        if (colorIndex != index) {
+            colorIndex = index;
+
+            if (listener != null) {
+                listener.onColorChanged(colorsDefined[colorIndex], fromUser);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Set the thumb position according to the selected color.
+     * The thumb will always be placed in the middle of the selected color.
+     */
+    private void updateThumbPositionByColorIndex() {
+        angle = ANGLE_BEGIN - (colorIndex + 0.5f) * radiantInterval;
+        invalidate();
+    }
+
+    private boolean updateThumbState(boolean dragThumb) {
+        if (this.dragThumb == dragThumb) {
+            // The state hasn't been changed; no need for updates.
+            return false;
+        }
+
+        this.dragThumb = dragThumb;
+        thumb.setState(dragThumb ? PRESSED_ENABLED_STATE_SET : ENABLED_STATE_SET);
+        invalidate();
+        return true;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/CropAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/CropAction.java
new file mode 100644
index 0000000..5e6dd53
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/CropAction.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.filters.CropFilter;
+
+/**
+ * An action handling crop effect.
+ */
+public class CropAction extends FilterAction {
+
+    private static final float DEFAULT_CROP = 0.2f;
+    private CropFilter filter;
+
+    public CropAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.crop_tooltip);
+    }
+
+    private RectF mapPhotoBounds(RectF bounds, RectF photoBounds) {
+        return new RectF((bounds.left - photoBounds.left) / photoBounds.width(),
+                (bounds.top - photoBounds.top) / photoBounds.height(),
+                (bounds.right - photoBounds.left) / photoBounds.width(),
+                (bounds.bottom - photoBounds.top) / photoBounds.height());
+    }
+
+    @Override
+    public void onBegin() {
+        filter = new CropFilter();
+
+        final RectF photoBounds = photoView.getPhotoDisplayBounds();
+        cropView.setPhotoBounds(new RectF(photoBounds));
+        cropView.setOnCropChangeListener(new CropView.OnCropChangeListener() {
+
+            @Override
+            public void onCropChanged(RectF bounds, boolean fromUser) {
+                if (fromUser) {
+                    filter.setCropBounds(mapPhotoBounds(bounds, photoBounds));
+                    notifyFilterChanged(filter, false);
+                }
+            }
+        });
+        RectF cropBounds = new RectF(photoBounds);
+        cropBounds.inset(photoBounds.width() * DEFAULT_CROP, photoBounds.height() * DEFAULT_CROP);
+        cropView.setCropBounds(cropBounds);
+        if (!cropView.fullPhotoCropped()) {
+            filter.setCropBounds(mapPhotoBounds(cropBounds, photoBounds));
+            notifyFilterChanged(filter, false);
+        }
+        cropView.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/CropView.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/CropView.java
new file mode 100644
index 0000000..6486c33
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/CropView.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.photoeditor.R;
+
+import java.util.Vector;
+
+/**
+ * A view that track touch motions and adjust crop bounds accordingly.
+ */
+class CropView extends View {
+
+    /**
+     * Listener of crop bounds.
+     */
+    public interface OnCropChangeListener {
+
+        void onCropChanged(RectF bounds, boolean fromUser);
+    }
+
+    private static final int TOUCH_AREA_NONE = 0;
+    private static final int TOUCH_AREA_LEFT = 1;
+    private static final int TOUCH_AREA_TOP = 2;
+    private static final int TOUCH_AREA_RIGHT = 4;
+    private static final int TOUCH_AREA_BOTTOM = 8;
+    private static final int TOUCH_AREA_INSIDE = 15;
+    private static final int TOUCH_AREA_OUTSIDE = 16;
+    private static final int TOUCH_AREA_TOP_LEFT = 3;
+    private static final int TOUCH_AREA_TOP_RIGHT = 6;
+    private static final int TOUCH_AREA_BOTTOM_LEFT = 9;
+    private static final int TOUCH_AREA_BOTTOM_RIGHT = 12;
+
+    private static final int BORDER_COLOR = 0xFF008AFF;
+    private static final int OUTER_COLOR = 0xA0000000;
+    private static final int INDICATION_COLOR = 0xFFCC9900;
+    private static final int TOUCH_AREA_SPAN = 20;
+    private static final int TOUCH_AREA_SPAN2 = TOUCH_AREA_SPAN * 2;
+    private static final float BORDER_WIDTH = 2.0f;
+
+    private final Paint outerAreaPaint;
+    private final Paint borderPaint;
+    private final Paint highlightPaint;
+
+    private final Drawable heightIndicator;
+    private final Drawable widthIndicator;
+    private final int indicatorSize;
+
+    private RectF cropBounds;
+    private RectF photoBounds;
+
+    private OnCropChangeListener listener;
+
+    private float lastX;
+    private float lastY;
+    private int currentTouchArea;
+
+    public CropView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources resources = context.getResources();
+        heightIndicator = resources.getDrawable(R.drawable.crop_height_holo);
+        widthIndicator = resources.getDrawable(R.drawable.crop_width_holo);
+        indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
+
+        outerAreaPaint = new Paint();
+        outerAreaPaint.setStyle(Paint.Style.FILL);
+        outerAreaPaint.setColor(OUTER_COLOR);
+
+        borderPaint = new Paint();
+        borderPaint.setStyle(Paint.Style.STROKE);
+        borderPaint.setColor(BORDER_COLOR);
+        borderPaint.setStrokeWidth(BORDER_WIDTH);
+
+        highlightPaint = new Paint();
+        highlightPaint.setStyle(Paint.Style.STROKE);
+        highlightPaint.setColor(INDICATION_COLOR);
+        highlightPaint.setStrokeWidth(BORDER_WIDTH);
+
+        currentTouchArea = TOUCH_AREA_NONE;
+    }
+
+    public void setOnCropChangeListener(OnCropChangeListener listener) {
+        this.listener = listener;
+    }
+
+    private void notifyCropChange(boolean fromUser) {
+        if (listener != null) {
+            listener.onCropChanged(cropBounds, fromUser);
+        }
+    }
+
+    public void setCropBounds(RectF bounds) {
+        bounds.intersect(photoBounds);
+        cropBounds = bounds;
+        if (photoBounds.width() <= TOUCH_AREA_SPAN2) {
+            cropBounds.left = photoBounds.left;
+            cropBounds.right = photoBounds.right;
+        }
+        if (photoBounds.height() <= TOUCH_AREA_SPAN2) {
+            cropBounds.top = photoBounds.top;
+            cropBounds.bottom = photoBounds.bottom;
+        }
+        notifyCropChange(false);
+        invalidate();
+    }
+
+    /**
+     * Sets bounds to crop within.
+     */
+    public void setPhotoBounds(RectF bounds) {
+        photoBounds = bounds;
+    }
+
+    public boolean fullPhotoCropped() {
+        return cropBounds.contains(photoBounds);
+    }
+
+    private int detectTouchArea(float x, float y) {
+        RectF area = new RectF();
+        area.set(cropBounds);
+        area.inset(-TOUCH_AREA_SPAN, -TOUCH_AREA_SPAN);
+        if (!area.contains(x, y)) {
+            return TOUCH_AREA_OUTSIDE;
+        }
+
+        // left
+        area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top  + TOUCH_AREA_SPAN,
+                cropBounds.left + TOUCH_AREA_SPAN, cropBounds.bottom - TOUCH_AREA_SPAN);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_LEFT;
+        }
+        // right
+        area.offset(cropBounds.width(), 0f);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_RIGHT;
+        }
+        // top
+        area.set(cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
+                cropBounds.right - TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_TOP;
+        }
+        // bottom
+        area.offset(0f, cropBounds.height());
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_BOTTOM;
+        }
+        // top left
+        area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
+                cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_TOP_LEFT;
+        }
+        // top right
+        area.offset(cropBounds.width(), 0f);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_TOP_RIGHT;
+        }
+        // bottom right
+        area.offset(0f, cropBounds.height());
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_BOTTOM_RIGHT;
+        }
+        // bottom left
+        area.offset(-cropBounds.width(), 0f);
+        if (area.contains(x, y)) {
+            return TOUCH_AREA_BOTTOM_LEFT;
+        }
+        return TOUCH_AREA_INSIDE;
+    }
+
+    private void performMove(float deltaX, float deltaY) {
+        if (currentTouchArea == TOUCH_AREA_INSIDE){  // moving the rect.
+            cropBounds.offset(deltaX, deltaY);
+            if (cropBounds.left < photoBounds.left) {
+                cropBounds.offset(photoBounds.left - cropBounds.left, 0f);
+            } else if (cropBounds.right > photoBounds.right) {
+                cropBounds.offset(photoBounds.right - cropBounds.right, 0f);
+            }
+            if (cropBounds.top < photoBounds.top) {
+                cropBounds.offset(0f, photoBounds.top - cropBounds.top);
+            } else if (cropBounds.bottom > photoBounds.bottom) {
+                cropBounds.offset(0f, photoBounds.bottom - cropBounds.bottom);
+            }
+        } else {  // adjusting bounds.
+            if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
+                cropBounds.left = Math.min(cropBounds.left + deltaX,
+                        cropBounds.right - TOUCH_AREA_SPAN2);
+            }
+            if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
+                cropBounds.top = Math.min(cropBounds.top + deltaY,
+                        cropBounds.bottom - TOUCH_AREA_SPAN2);
+            }
+            if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
+                cropBounds.right = Math.max(cropBounds.right + deltaX,
+                        cropBounds.left + TOUCH_AREA_SPAN2);
+            }
+            if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
+                cropBounds.bottom = Math.max(cropBounds.bottom + deltaY,
+                        cropBounds.top + TOUCH_AREA_SPAN2);
+            }
+            cropBounds.intersect(photoBounds);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (!isEnabled()) {
+            return true;
+        }
+        float x = event.getX();
+        float y = event.getY();
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                currentTouchArea = detectTouchArea(x, y);
+                lastX = x;
+                lastY = y;
+                invalidate();
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                performMove(x - lastX, y - lastY);
+
+                lastX = x;
+                lastY = y;
+                notifyCropChange(true);
+                invalidate();
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                currentTouchArea = TOUCH_AREA_NONE;
+                invalidate();
+                break;
+        }
+        return true;
+    }
+
+    private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
+        int left = (int) centerX - indicatorSize / 2;
+        int top = (int) centerY - indicatorSize / 2;
+        int right = left + indicatorSize;
+        int bottom = top + indicatorSize;
+        indicator.setBounds(left, top, right, bottom);
+        indicator.draw(canvas);
+    }
+
+    private void drawIndicators(Canvas canvas) {
+        drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.top);
+        drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.bottom);
+        drawIndicator(canvas, widthIndicator, cropBounds.left, cropBounds.centerY());
+        drawIndicator(canvas, widthIndicator, cropBounds.right, cropBounds.centerY());
+    }
+
+    private void drawTouchHighlights(Canvas canvas) {
+        if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
+            canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.right, cropBounds.top,
+                    highlightPaint);
+        }
+        if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
+            canvas.drawLine(cropBounds.left, cropBounds.bottom, cropBounds.right,
+                    cropBounds.bottom, highlightPaint);
+        }
+        if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
+            canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.left, cropBounds.bottom,
+                    highlightPaint);
+        }
+        if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
+            canvas.drawLine(cropBounds.right, cropBounds.top, cropBounds.right, cropBounds.bottom,
+                    highlightPaint);
+        }
+    }
+
+    private void drawBounds(Canvas canvas) {
+        Rect r = new Rect();
+        photoBounds.roundOut(r);
+        Region drawRegion = new Region(r);
+        cropBounds.roundOut(r);
+        drawRegion.op(r, Region.Op.DIFFERENCE);
+        RegionIterator iter = new RegionIterator(drawRegion);
+        while (iter.next(r)) {
+            canvas.drawRect(r, outerAreaPaint);
+        }
+
+        canvas.drawRect(cropBounds, borderPaint);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        drawBounds(canvas);
+        if (currentTouchArea != TOUCH_AREA_NONE) {
+            drawTouchHighlights(canvas);
+            drawIndicators(canvas);
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/CrossProcessAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/CrossProcessAction.java
new file mode 100644
index 0000000..c4565ee
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/CrossProcessAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.CrossProcessFilter;
+
+/**
+ * An action handling cross-process effect.
+ */
+public class CrossProcessAction extends FilterAction {
+
+    public CrossProcessAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new CrossProcessFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/DocumentaryAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/DocumentaryAction.java
new file mode 100644
index 0000000..b8bd9c6
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/DocumentaryAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.DocumentaryFilter;
+
+/**
+ * An action handling the preset "Documentary" effect.
+ */
+public class DocumentaryAction extends FilterAction {
+
+    public DocumentaryAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new DocumentaryFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleAction.java
new file mode 100644
index 0000000..16cc37e
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.graphics.Path;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.filters.DoodleFilter;
+
+/**
+ * An action handling doodle effect.
+ */
+public class DoodleAction extends FilterAction {
+
+    private static final int DEFAULT_COLOR_INDEX = 4;
+
+    private DoodleFilter filter;
+
+    public DoodleAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.doodle_tooltip);
+    }
+
+    @Override
+    public void onBegin() {
+        filter = new DoodleFilter();
+
+        colorWheel.setOnColorChangeListener(new ColorWheel.OnColorChangeListener() {
+
+            @Override
+            public void onColorChanged(int color, boolean fromUser){
+                if (fromUser) {
+                    doodleView.startPath(color);
+                    filter.addPath(color);
+                }
+            }
+        });
+        colorWheel.setColorIndex(DEFAULT_COLOR_INDEX);
+        colorWheel.setVisibility(View.VISIBLE);
+
+        // Directly draw on doodle-view instead of waiting for top-filter output callback.
+        doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
+
+            private final Path transformPath = new Path();
+
+            @Override
+            public void onLastPathChanged(Path path) {
+                photoView.mapPhotoPath(path, transformPath);
+                filter.updateLastPath(transformPath);
+                notifyFilterChanged(filter, false);
+            }
+        });
+        doodleView.clear();
+        doodleView.clipBounds(photoView.getPhotoDisplayBounds());
+        doodleView.setVisibility(View.VISIBLE);
+
+        int color = colorWheel.getColor();
+        doodleView.startPath(color);
+
+        filter.addPath(color);
+    }
+
+    @Override
+    public void onEnd() {
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleView.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleView.java
new file mode 100644
index 0000000..e975965
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/DoodleView.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.Vector;
+
+/**
+ * A view that track touch motions as paths and paint them as doodles.
+ */
+class DoodleView extends View {
+
+    /**
+     * Listener of doodle paths.
+     */
+    public interface OnDoodleChangeListener {
+
+        void onLastPathChanged(Path path);
+    }
+
+    private final Vector<ColorPath> colorPaths = new Vector<ColorPath>();
+    private final Paint paint = ColorPath.createPaint();
+
+    private OnDoodleChangeListener listener;
+    private RectF clipBounds;
+    private float lastX;
+    private float lastY;
+
+    public DoodleView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setOnDoodleChangeListener(OnDoodleChangeListener listener) {
+        this.listener = listener;
+    }
+
+    public void startPath(int color) {
+        // Remove last empty path before adding a new path.
+        if (!colorPaths.isEmpty() && colorPaths.lastElement().path().isEmpty()) {
+            colorPaths.remove(colorPaths.size() - 1);
+        }
+        colorPaths.add(new ColorPath(color, new Path()));
+        colorPaths.lastElement().path().moveTo(lastX, lastY);
+    }
+
+    /**
+     * Clears clip bounds and paths drawn.
+     */
+    public void clear() {
+        colorPaths.clear();
+        clipBounds = null;
+    }
+
+    /**
+     * Clips bounds for paths being drawn.
+     */
+    public void clipBounds(RectF bounds) {
+        clipBounds = bounds;
+    }
+
+    private void pathUpdated(Path path) {
+        invalidate();
+        if (listener != null) {
+            listener.onLastPathChanged(path);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (isEnabled() && !colorPaths.isEmpty()) {
+            Path path = colorPaths.lastElement().path();
+            float x = event.getX();
+            float y = event.getY();
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    path.moveTo(x, y);
+                    pathUpdated(path);
+                    lastX = x;
+                    lastY = y;
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    path.quadTo(lastX, lastY, (lastX + x) / 2, (lastY + y) / 2);
+                    pathUpdated(path);
+                    lastX = x;
+                    lastY = y;
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    // Line to last position with offset to draw at least dots for single clicks.
+                    path.lineTo(lastX + 1, lastY + 1);
+                    pathUpdated(path);
+                    break;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        canvas.save();
+        if (clipBounds != null) {
+            canvas.clipRect(clipBounds);
+        }
+        for (ColorPath colorPath : colorPaths) {
+            colorPath.draw(canvas, paint);
+        }
+        canvas.restore();
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/DuotoneAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/DuotoneAction.java
new file mode 100644
index 0000000..80eaf0f
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/DuotoneAction.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.DuotoneFilter;
+
+/**
+ * An action handling duo-tone effect.
+ */
+public class DuotoneAction extends FilterAction {
+
+    private static final int DEFAULT_FIRST_COLOR = 0x004488;
+    private static final int DEFAULT_SECOND_COLOR = 0xffff00;
+
+    public DuotoneAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        // TODO: Add several sets of duo-tone colors to select from.
+        DuotoneFilter filter = new DuotoneFilter();
+        filter.setDuotone(DEFAULT_FIRST_COLOR, DEFAULT_SECOND_COLOR);
+        notifyFilterChanged(filter, true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/FillLightAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/FillLightAction.java
new file mode 100644
index 0000000..60ddcd3
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/FillLightAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.FillLightFilter;
+
+/**
+ * An action handling fill-light effect.
+ */
+public class FillLightAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    public FillLightAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final FillLightFilter filter = new FillLightFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setBacklight(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/FilterAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/FilterAction.java
new file mode 100644
index 0000000..08c9fd2
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/FilterAction.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.Photo;
+import com.android.photoeditor.PhotoOutputCallback;
+import com.android.photoeditor.PhotoView;
+import com.android.photoeditor.R;
+import com.android.photoeditor.SpinnerProgressDialog;
+import com.android.photoeditor.filters.Filter;
+
+/**
+ * An action binding UI controls and filter operation for editing photo.
+ */
+public abstract class FilterAction {
+
+    /**
+     * Listens to when this FilterAction has done editing and should be ended.
+     */
+    public interface FilterActionListener {
+
+        void onDone();
+    }
+
+    protected final FilterStack filterStack;
+    protected final PhotoView photoView;
+    protected final ScaleWheel scaleWheel;
+    protected final ColorWheel colorWheel;
+    protected final DoodleView doodleView;
+    protected final TouchView touchView;
+    protected final RotateView rotateView;
+    protected final CropView cropView;
+    private final ViewGroup tools;
+
+    private Toast tooltip;
+    private boolean pushedFilter;
+    private OutputCallback lastOutputCallback;
+    private FilterActionListener listener;
+
+    public FilterAction(FilterStack filterStack, ViewGroup tools) {
+        this.filterStack = filterStack;
+        this.tools = tools;
+
+        photoView = (PhotoView) tools.findViewById(R.id.photo_view);
+        scaleWheel = (ScaleWheel) tools.findViewById(R.id.scale_wheel);
+        colorWheel = (ColorWheel) tools.findViewById(R.id.color_wheel);
+        doodleView = (DoodleView) tools.findViewById(R.id.doodle_view);
+        touchView = (TouchView) tools.findViewById(R.id.touch_view);
+        rotateView = (RotateView) tools.findViewById(R.id.rotate_view);
+        cropView = (CropView) tools.findViewById(R.id.crop_view);
+    }
+
+    public FilterAction(FilterStack filterStack, ViewGroup tools, int tooltipId) {
+        this(filterStack, tools);
+
+        tooltip = Toast.makeText(tools.getContext(), tooltipId, Toast.LENGTH_SHORT);
+    }
+
+    protected void notifyFilterChanged(Filter filter, boolean output) {
+        if (!pushedFilter && filter.isValid()) {
+            filterStack.pushFilter(filter);
+            pushedFilter = true;
+        }
+        if (pushedFilter && output) {
+            // Notify the stack to output the changed top filter.
+            lastOutputCallback = new OutputCallback();
+            filterStack.topFilterChanged(lastOutputCallback);
+        }
+    }
+
+    public void begin(FilterActionListener listener) {
+        this.listener = listener;
+        if (tooltip != null) {
+            tooltip.show();
+        }
+        onBegin();
+    }
+
+    public void end() {
+        onEnd();
+
+        // Wait till last output callback is done before finishing.
+        if ((lastOutputCallback == null) || lastOutputCallback.done) {
+            finish();
+        } else {
+            final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(tools);
+            lastOutputCallback.runnableOnDone = new Runnable() {
+
+                @Override
+                public void run() {
+                    progressDialog.dismiss();
+                    finish();
+                }
+            };
+        }
+    }
+
+    private void finish() {
+        // Close the tooltip if it's still showing.
+        if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
+            tooltip.cancel();
+        }
+        if (scaleWheel.getVisibility() == View.VISIBLE) {
+            scaleWheel.setOnScaleChangeListener(null);
+            scaleWheel.setVisibility(View.INVISIBLE);
+        }
+        if (colorWheel.getVisibility() == View.VISIBLE) {
+            colorWheel.setOnColorChangeListener(null);
+            colorWheel.setVisibility(View.INVISIBLE);
+        }
+        if (doodleView.getVisibility() == View.VISIBLE) {
+            doodleView.setOnDoodleChangeListener(null);
+            doodleView.setVisibility(View.INVISIBLE);
+        }
+        if (touchView.getVisibility() == View.VISIBLE) {
+            touchView.setSingleTapListener(null);
+            touchView.setSwipeListener(null);
+            touchView.setVisibility(View.INVISIBLE);
+        }
+        if (rotateView.getVisibility() == View.VISIBLE) {
+            rotateView.setOnAngleChangeListener(null);
+            rotateView.setVisibility(View.INVISIBLE);
+        }
+        if (cropView.getVisibility() == View.VISIBLE) {
+            cropView.setOnCropChangeListener(null);
+            cropView.setVisibility(View.INVISIBLE);
+        }
+        photoView.clipPhoto(null);
+        // Notify the listener that this action is done finishing.
+        listener.onDone();
+        listener = null;
+        lastOutputCallback = null;
+        pushedFilter = false;
+    }
+
+    /**
+     * Called when the action is about to begin; subclasses should creates a specific filter and
+     * binds the filter to necessary UI controls here.
+     */
+    protected abstract void onBegin();
+
+    /**
+     * Called when the action is about to end; subclasses could do specific ending operations here.
+     */
+    protected abstract void onEnd();
+
+    /**
+     * Output callback for top filter changes.
+     */
+    private class OutputCallback implements PhotoOutputCallback {
+
+        private boolean done;
+        private Runnable runnableOnDone;
+
+        @Override
+        public void onReady(Photo photo) {
+            photoView.update(photo);
+            done = true;
+
+            if (runnableOnDone != null) {
+                runnableOnDone.run();
+            }
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/FisheyeAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/FisheyeAction.java
new file mode 100644
index 0000000..efd00e6
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/FisheyeAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.FisheyeFilter;
+
+/**
+ * An action handling fisheye effect.
+ */
+public class FisheyeAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public FisheyeAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final FisheyeFilter filter = new FisheyeFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/FlipAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/FlipAction.java
new file mode 100644
index 0000000..26402f7
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/FlipAction.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.animation.Rotate3DAnimation;
+import com.android.photoeditor.filters.FlipFilter;
+
+/**
+ * An action handling flip effect.
+ */
+public class FlipAction extends FilterAction {
+
+    private static final int ANIMATION_DURATION = 500;
+
+    private boolean flipHorizontal;
+    private boolean flipVertical;
+
+    public FlipAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.flip_tooltip);
+    }
+
+    @Override
+    public void onBegin() {
+        final FlipFilter filter = new FlipFilter();
+
+        touchView.setSwipeListener(new TouchView.SwipeListener() {
+
+            @Override
+            public void onSwipeDown() {
+                setFlipAnimations(Rotate3DAnimation.Rotate.DOWN);
+                flipFilterVertically(filter);
+            }
+
+            @Override
+            public void onSwipeLeft() {
+                setFlipAnimations(Rotate3DAnimation.Rotate.LEFT);
+                flipFilterHorizontally(filter);
+            }
+
+            @Override
+            public void onSwipeRight() {
+                setFlipAnimations(Rotate3DAnimation.Rotate.RIGHT);
+                flipFilterHorizontally(filter);
+            }
+
+            @Override
+            public void onSwipeUp() {
+                setFlipAnimations(Rotate3DAnimation.Rotate.UP);
+                flipFilterVertically(filter);
+            }
+        });
+        touchView.setVisibility(View.VISIBLE);
+
+        flipHorizontal = false;
+        flipVertical = false;
+        setFlipAnimations(Rotate3DAnimation.Rotate.RIGHT);
+        flipFilterHorizontally(filter);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+
+    private void setFlipAnimations(Rotate3DAnimation.Rotate rotate) {
+        photoView.setTransitionAnimations(
+                Rotate3DAnimation.getFlipAnimations(rotate, ANIMATION_DURATION));
+    }
+
+    private void flipFilterHorizontally(final FlipFilter filter) {
+        flipHorizontal = !flipHorizontal;
+        filter.setFlip(flipHorizontal, flipVertical);
+        notifyFilterChanged(filter, true);
+    }
+
+    private void flipFilterVertically(final FlipFilter filter) {
+        flipVertical = !flipVertical;
+        filter.setFlip(flipHorizontal, flipVertical);
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/GrainAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/GrainAction.java
new file mode 100644
index 0000000..a841d5d
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/GrainAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.GrainFilter;
+
+/**
+ * An action handling the film-grain effect.
+ */
+public class GrainAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public GrainAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final GrainFilter filter = new GrainFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/GrayscaleAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/GrayscaleAction.java
new file mode 100644
index 0000000..aa13f58
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/GrayscaleAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.GrayscaleFilter;
+
+/**
+ * An action handling grayscale effect.
+ */
+public class GrayscaleAction extends FilterAction {
+
+    public GrayscaleAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new GrayscaleFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/HighlightAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/HighlightAction.java
new file mode 100644
index 0000000..a5cc9d9
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/HighlightAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.HighlightFilter;
+
+/**
+ * An action handling highlight effect.
+ */
+public class HighlightAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    public HighlightAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final HighlightFilter filter = new HighlightFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setHighlight(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/LomoishAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/LomoishAction.java
new file mode 100644
index 0000000..0642023
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/LomoishAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.LomoishFilter;
+
+/**
+ * An action handling the preset "Lomo-ish" effect.
+ */
+public class LomoishAction extends FilterAction {
+
+    public LomoishAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new LomoishFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/NegativeAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/NegativeAction.java
new file mode 100644
index 0000000..421d5cc
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/NegativeAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.NegativeFilter;
+
+/**
+ * An action handling negative effect.
+ */
+public class NegativeAction extends FilterAction {
+
+    public NegativeAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new NegativeFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/PosterizeAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/PosterizeAction.java
new file mode 100644
index 0000000..e1c0528
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/PosterizeAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.PosterizeFilter;
+
+/**
+ * An action handling the "Posterize" effect.
+ */
+public class PosterizeAction extends FilterAction {
+
+    public PosterizeAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new PosterizeFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/RedEyeAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/RedEyeAction.java
new file mode 100644
index 0000000..9a36adb
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/RedEyeAction.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.filters.RedEyeFilter;
+
+/**
+ * An action handling red-eye removal.
+ */
+public class RedEyeAction extends FilterAction {
+
+    public RedEyeAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.redeye_tooltip);
+    }
+
+    @Override
+    public void onBegin() {
+        final RedEyeFilter filter = new RedEyeFilter();
+
+        touchView.setSingleTapListener(new TouchView.SingleTapListener() {
+
+            @Override
+            public void onSingleTap(float x, float y) {
+                filter.addRedEyePosition(photoView.mapPhotoPoint(x, y));
+                notifyFilterChanged(filter, true);
+            }
+        });
+        touchView.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateAction.java
new file mode 100644
index 0000000..a04f9d3
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateAction.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.RectUtils;
+import com.android.photoeditor.filters.RotateFilter;
+
+/**
+ * An action handling rotate effect.
+ */
+public class RotateAction extends FilterAction {
+
+    private static final float DEFAULT_ANGLE = 0.0f;
+    private static final float DEFAULT_ROTATE_SPAN = 360.0f;
+
+    private RotateFilter filter;
+    private float rotateDegrees;
+
+    public RotateAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.rotate_tooltip);
+    }
+
+    @Override
+    public void onBegin() {
+        filter = new RotateFilter();
+        rotateDegrees = 0;
+
+        final Matrix matrix = new Matrix();
+        final RectF rotateBounds = new RectF();
+        final RectF photoBounds = photoView.getPhotoBounds();
+
+        // Directly transform photo-view instead of waiting for top-filter output callback.
+        rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() {
+
+            @Override
+            public void onAngleChanged(float degrees, boolean fromUser){
+                if (fromUser) {
+                    rotateDegrees = degrees;
+                    filter.setAngle(degrees);
+                    notifyFilterChanged(filter, false);
+                    transformPhotoView(degrees);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch() {
+                // no-op
+            }
+
+            @Override
+            public void onStopTrackingTouch() {
+                if (roundFilterRotationDegrees()) {
+                    notifyFilterChanged(filter, false);
+                    transformPhotoView(rotateDegrees);
+                    rotateView.setRotatedAngle(rotateDegrees);
+                }
+            }
+
+            private void transformPhotoView(float degrees) {
+                matrix.reset();
+                rotateBounds.set(photoBounds);
+                RectUtils.postRotateMatrix(degrees, rotateBounds, matrix);
+                float scale = RectUtils.getDisplayScale(rotateBounds, photoView);
+                matrix.postScale(scale, scale);
+                photoView.transformDisplay(matrix);
+            }
+        });
+        rotateView.setGridBounds(null);
+        rotateView.setRotatedAngle(DEFAULT_ANGLE);
+        rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
+        rotateView.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+        // Round the current rotation degrees in case rotation tracking has not stopped yet.
+        roundFilterRotationDegrees();
+        notifyFilterChanged(filter, true);
+    }
+
+    /**
+     * Rounds filter rotation degrees to multiples of 90 degrees.
+     *
+     * @return true if the rotation degrees has been changed.
+     */
+    private boolean roundFilterRotationDegrees() {
+        if (rotateDegrees % 90 != 0) {
+            rotateDegrees = Math.round(rotateDegrees / 90) * 90;
+            filter.setAngle(rotateDegrees);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateView.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateView.java
new file mode 100644
index 0000000..b0cea16
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/RotateView.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * View that shows grids and handles touch-events to adjust angle of rotation.
+ */
+class RotateView extends View {
+
+    /**
+     * Listens to rotate changes.
+     */
+    public interface OnRotateChangeListener {
+
+        void onAngleChanged(float degrees, boolean fromUser);
+
+        void onStartTrackingTouch();
+
+        void onStopTrackingTouch();
+    }
+
+    // All angles used are defined between PI and -PI.
+    private static final float MATH_PI = (float) Math.PI;
+    private static final float MATH_HALF_PI = MATH_PI / 2;
+    private static final float RADIAN_TO_DEGREE = 180f / MATH_PI;
+
+    private final Paint dashStrokePaint;
+    private final Path grids = new Path();
+    private final Path referenceLine = new Path();
+    private final RectF referenceLineBounds = new RectF();
+
+    private OnRotateChangeListener listener;
+    private int centerX;
+    private int centerY;
+    private float maxRotatedAngle;
+    private float minRotatedAngle;
+    private float currentRotatedAngle;
+    private float lastRotatedAngle;
+    private float touchStartAngle;
+
+    public RotateView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        dashStrokePaint = new Paint();
+        dashStrokePaint.setAntiAlias(true);
+        dashStrokePaint.setStyle(Paint.Style.STROKE);
+        dashStrokePaint.setPathEffect(new DashPathEffect(new float[] {15.0f, 5.0f}, 1.0f));
+    }
+
+    public void setRotatedAngle(float degrees) {
+        currentRotatedAngle = -degrees / RADIAN_TO_DEGREE;
+        notifyAngleChange(false);
+    }
+
+    /**
+     * Sets allowed degrees for rotation span before rotating the view.
+     */
+    public void setRotateSpan(float degrees) {
+        if (degrees >= 360f) {
+            maxRotatedAngle = Float.POSITIVE_INFINITY;
+        } else {
+            maxRotatedAngle = (degrees / RADIAN_TO_DEGREE) / 2;
+        }
+        minRotatedAngle = -maxRotatedAngle;
+    }
+
+    /**
+     * Sets grid bounds to be drawn or null to hide grids right before the view is visible.
+     */
+    public void setGridBounds(RectF bounds) {
+        grids.reset();
+        referenceLine.reset();
+        if (bounds != null) {
+            float delta = bounds.width() / 4.0f;
+            for (float x = bounds.left + delta; x < bounds.right; x += delta) {
+                grids.moveTo(x, bounds.top);
+                grids.lineTo(x, bounds.bottom);
+            }
+            delta = bounds.height() / 4.0f;
+            for (float y = bounds.top + delta; y < bounds.bottom; y += delta) {
+                grids.moveTo(bounds.left, y);
+                grids.lineTo(bounds.right, y);
+            }
+
+            // Make reference line long enough to cross the bounds diagonally after being rotated.
+            referenceLineBounds.set(bounds);
+            float radius = (float) Math.hypot(centerX, centerY);
+            delta = radius - centerX;
+            referenceLine.moveTo(-delta, centerY);
+            referenceLine.lineTo(getWidth() + delta, centerY);
+
+            delta = radius - centerY;
+            referenceLine.moveTo(centerX, -delta);
+            referenceLine.lineTo(centerX, getHeight() + delta);
+        }
+        invalidate();
+    }
+
+    public void setOnAngleChangeListener(OnRotateChangeListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        centerX = w / 2;
+        centerY = h / 2;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (!grids.isEmpty()) {
+            dashStrokePaint.setStrokeWidth(2f);
+            dashStrokePaint.setColor(0x99CCCCCC);
+            canvas.drawPath(grids, dashStrokePaint);
+        }
+
+        if (!referenceLine.isEmpty()) {
+            dashStrokePaint.setStrokeWidth(2f);
+            dashStrokePaint.setColor(0x99FFCC77);
+            canvas.save();
+            canvas.clipRect(referenceLineBounds);
+            canvas.rotate(-currentRotatedAngle * RADIAN_TO_DEGREE, centerX, centerY);
+            canvas.drawPath(referenceLine, dashStrokePaint);
+            canvas.restore();
+        }
+    }
+
+    private float calculateAngle(MotionEvent ev) {
+        float x = ev.getX() - centerX;
+        float y = centerY - ev.getY();
+
+        float angle;
+        if (x == 0) {
+            angle = (y >= 0) ? MATH_HALF_PI : -MATH_HALF_PI;
+        } else {
+            angle = (float) Math.atan(y / x);
+        }
+
+        if ((angle >= 0) && (x < 0)) {
+            angle = angle - MATH_PI;
+        } else if ((angle < 0) && (x < 0)) {
+            angle = MATH_PI + angle;
+        }
+        return angle;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        super.onTouchEvent(ev);
+
+        if (isEnabled()) {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    lastRotatedAngle = currentRotatedAngle;
+                    touchStartAngle = calculateAngle(ev);
+
+                    if (listener != null) {
+                        listener.onStartTrackingTouch();
+                    }
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    float touchAngle = calculateAngle(ev);
+                    float rotatedAngle = touchAngle - touchStartAngle + lastRotatedAngle;
+
+                    if ((rotatedAngle > maxRotatedAngle) || (rotatedAngle < minRotatedAngle)) {
+                        // Angles are out of range; restart rotating.
+                        // TODO: Fix discontinuity around boundary.
+                        lastRotatedAngle = currentRotatedAngle;
+                        touchStartAngle = touchAngle;
+                    } else {
+                        currentRotatedAngle = rotatedAngle;
+                        notifyAngleChange(true);
+                        invalidate();
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    if (listener != null) {
+                        listener.onStopTrackingTouch();
+                    }
+                    break;
+            }
+        }
+        return true;
+    }
+
+    private void notifyAngleChange(boolean fromUser) {
+        if (listener != null) {
+            listener.onAngleChanged(-currentRotatedAngle * RADIAN_TO_DEGREE, fromUser);
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/SaturationAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/SaturationAction.java
new file mode 100644
index 0000000..aad4178
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/SaturationAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.SaturationFilter;
+
+/**
+ * An action handling saturation effect.
+ */
+public class SaturationAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public SaturationAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final SaturationFilter filter = new SaturationFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setSaturation(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/ScaleWheel.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/ScaleWheel.java
new file mode 100644
index 0000000..fd25c87
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/ScaleWheel.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+
+import com.android.photoeditor.R;
+
+/**
+ * Wheel that has a draggable thumb to set and get the normalized scale value from 0 to 1.
+ */
+class ScaleWheel extends View {
+
+    /**
+     * Listens to scale changes.
+     */
+    public interface OnScaleChangeListener {
+
+        void onProgressChanged(float progress, boolean fromUser);
+    }
+
+    private static final float MATH_PI = (float) Math.PI;
+    private static final float MATH_HALF_PI = MATH_PI / 2;
+
+    // Angles are defined between PI and -PI.
+    private static final float ANGLE_SPANNED = MATH_PI * 4 / 3;
+    private static final float ANGLE_BEGIN = ANGLE_SPANNED / 2.0f;
+
+    private static final float THUMB_RADIUS_RATIO = 0.363f;
+    private static final float INNER_RADIUS_RATIO = 0.24f;
+
+    private final Drawable thumb;
+    private final Drawable background;
+    private final int thumbSize;
+    private final Paint circlePaint;
+    private final int maxProgress;
+    private final float radiantInterval;
+    private int thumbRadius;
+    private int innerRadius;
+    private int centerXY;
+    private float angle;
+    private int progress;
+    private boolean dragThumb;
+    private OnScaleChangeListener listener;
+
+    public ScaleWheel(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources resources = context.getResources();
+        thumbSize = (int) resources.getDimension(R.dimen.wheel_thumb_size);
+
+        // Set the maximum progress and compute the radiant interval between progress values.
+        maxProgress = 100;
+        radiantInterval = ANGLE_SPANNED / maxProgress;
+
+        thumb = resources.getDrawable(R.drawable.wheel_knot_selector);
+        background = resources.getDrawable(R.drawable.scale_wheel_background);
+        background.setAlpha(160);
+
+        circlePaint = new Paint();
+        circlePaint.setAntiAlias(true);
+        circlePaint.setColor(resources.getColor(R.color.scale_wheel_interior_color));
+    }
+
+    public void setProgress(float progress) {
+        if (updateProgress((int) (progress * maxProgress), false)) {
+            updateThumbPositionByProgress();
+        }
+    }
+
+    public void setOnScaleChangeListener(OnScaleChangeListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+
+        startAnimation(AnimationUtils.loadAnimation(getContext(),
+                (visibility == VISIBLE) ? R.anim.wheel_show : R.anim.wheel_hide));
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        int wheelSize = Math.min(w, h);
+        thumbRadius = (int) (wheelSize * THUMB_RADIUS_RATIO);
+        innerRadius = (int) (wheelSize * INNER_RADIUS_RATIO);
+
+        // The wheel would be centered at (centerXY, centerXY) and have outer-radius centerXY.
+        centerXY = wheelSize / 2;
+        updateThumbPositionByProgress();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        background.setBounds(0, 0, getWidth(), getHeight());
+        background.draw(canvas);
+
+        int thumbX = (int) (thumbRadius * Math.cos(angle) + centerXY);
+        int thumbY = (int) (centerXY - thumbRadius * Math.sin(angle));
+        int halfSize = thumbSize / 2;
+        thumb.setBounds(thumbX - halfSize, thumbY - halfSize, thumbX + halfSize, thumbY + halfSize);
+        thumb.draw(canvas);
+
+        float radius = (progress * innerRadius) / maxProgress;
+        canvas.drawCircle(centerXY, centerXY, radius, circlePaint);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        super.onTouchEvent(ev);
+
+        if (isEnabled()) {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    updateThumbState(
+                            isHittingThumbArea(ev.getX() - centerXY, centerXY - ev.getY()));
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    float x = ev.getX() - centerXY;
+                    float y = centerXY - ev.getY();
+                    if (!dragThumb && !updateThumbState(isHittingThumbArea(x, y))) {
+                        // The thumb wasn't dragged and isn't being dragged, either.
+                        break;
+                    }
+
+                    if (updateAngle(x, y)) {
+                        int progress = (int) ((ANGLE_BEGIN - angle) / radiantInterval);
+                        if (updateProgress(progress, true)) {
+                            invalidate();
+                        }
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    updateThumbState(false);
+                    break;
+            }
+        }
+        return true;
+    }
+
+    private boolean isHittingThumbArea(float x, float y) {
+        double radius = Math.sqrt((x * x) + (y * y));
+        return (radius > innerRadius) && (radius < centerXY);
+    }
+
+    private boolean updateAngle(float x, float y) {
+        float angle;
+        if (x == 0) {
+            angle = (y >= 0) ? MATH_HALF_PI : -MATH_HALF_PI;
+        } else {
+            angle = (float) Math.atan(y / x);
+        }
+
+        if ((angle >= 0) && (x < 0)) {
+            angle = angle - MATH_PI;
+        } else if ((angle < 0) && (x < 0)) {
+            angle = MATH_PI + angle;
+        }
+
+        if ((angle > ANGLE_BEGIN) || (angle <= ANGLE_BEGIN - ANGLE_SPANNED)) {
+            return false;
+        }
+
+        this.angle = angle;
+        return true;
+    }
+
+    private boolean updateProgress(int progress, boolean fromUser) {
+        if ((this.progress != progress) && (progress >= 0) && (progress <= maxProgress)) {
+            this.progress = progress;
+
+            if (listener != null) {
+                listener.onProgressChanged((float) progress / maxProgress, fromUser);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void updateThumbPositionByProgress() {
+        angle = ANGLE_BEGIN - progress * radiantInterval;
+        invalidate();
+    }
+
+    private boolean updateThumbState(boolean dragThumb) {
+        if (this.dragThumb == dragThumb) {
+            // The state hasn't been changed; no need for updates.
+            return false;
+        }
+
+        this.dragThumb = dragThumb;
+        thumb.setState(dragThumb ? PRESSED_ENABLED_STATE_SET : ENABLED_STATE_SET);
+        invalidate();
+        return true;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/SepiaAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/SepiaAction.java
new file mode 100644
index 0000000..26eea2e
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/SepiaAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.SepiaFilter;
+
+/**
+ * An action handling sepia effect.
+ */
+public class SepiaAction  extends FilterAction {
+
+    public SepiaAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new SepiaFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/ShadowAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/ShadowAction.java
new file mode 100644
index 0000000..b159ba5
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/ShadowAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.ShadowFilter;
+
+/**
+ * An action handling shadow effect.
+ */
+public class ShadowAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    public ShadowAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final ShadowFilter filter = new ShadowFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setShadow(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/SharpenAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/SharpenAction.java
new file mode 100644
index 0000000..1c0542f
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/SharpenAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.SharpenFilter;
+
+/**
+ * An action handling sharpen effect.
+ */
+public class SharpenAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public SharpenAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final SharpenFilter filter = new SharpenFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setSharpen(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+
+        filter.setSharpen(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/SoftFocusAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/SoftFocusAction.java
new file mode 100644
index 0000000..34dc201
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/SoftFocusAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.SoftFocusFilter;
+
+/**
+ * An action handling soft focus effect.
+ */
+public class SoftFocusAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    public SoftFocusAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final SoftFocusFilter filter = new SoftFocusFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setSoftFocus(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/StraightenAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/StraightenAction.java
new file mode 100644
index 0000000..0e84d9b
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/StraightenAction.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.R;
+import com.android.photoeditor.RectUtils;
+import com.android.photoeditor.filters.StraightenFilter;
+
+/**
+ * An action handling straighten effect.
+ */
+public class StraightenAction extends FilterAction {
+
+    private static final float DEFAULT_ANGLE = 0.0f;
+    private static final float DEFAULT_ROTATE_SPAN = 60.0f;
+
+    private StraightenFilter filter;
+
+    public StraightenAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools, R.string.straighten_tooltip);
+    }
+
+    @Override
+    public void onBegin() {
+        filter = new StraightenFilter();
+
+        final Matrix matrix = new Matrix();
+        final RectF photoBounds = photoView.getPhotoBounds();
+        final float displayScale = RectUtils.getDisplayScale(photoBounds, photoView);
+
+        RectF displayBounds = photoView.getPhotoDisplayBounds();
+        photoView.clipPhoto(displayBounds);
+
+        // Directly transform photo-view instead of waiting for top-filter output callback.
+        rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() {
+
+            @Override
+            public void onAngleChanged(float angle, boolean fromUser){
+                if (fromUser) {
+                    filter.setAngle(angle);
+                    notifyFilterChanged(filter, false);
+                    RectUtils.getStraightenMatrix(photoBounds, angle, matrix);
+                    matrix.postScale(displayScale, displayScale);
+                    photoView.transformDisplay(matrix);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch() {
+                // no-op
+            }
+
+            @Override
+            public void onStopTrackingTouch() {
+                // no-op
+            }
+        });
+        rotateView.setGridBounds(displayBounds);
+        rotateView.setRotatedAngle(DEFAULT_ANGLE);
+        rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
+        rotateView.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onEnd() {
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/TintAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/TintAction.java
new file mode 100644
index 0000000..23cca2b
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/TintAction.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.TintFilter;
+
+/**
+ * An action handling tint effect.
+ */
+public class TintAction extends FilterAction {
+
+    private static final int DEFAULT_COLOR_INDEX = 13;
+
+    public TintAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final TintFilter filter = new TintFilter();
+
+        colorWheel.setOnColorChangeListener(new ColorWheel.OnColorChangeListener() {
+
+            @Override
+            public void onColorChanged(int color, boolean fromUser){
+                if (fromUser) {
+                    filter.setTint(color);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        // Tint photo with the default color.
+        colorWheel.setColorIndex(DEFAULT_COLOR_INDEX);
+        colorWheel.setVisibility(View.VISIBLE);
+
+        filter.setTint(colorWheel.getColor());
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/TouchView.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/TouchView.java
new file mode 100644
index 0000000..5c000fb
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/TouchView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * A view that detects user gestures and touch motions.
+ */
+class TouchView extends View {
+
+    /**
+     * Listener of swipes.
+     */
+    public interface SwipeListener {
+
+        void onSwipeLeft();
+
+        void onSwipeRight();
+
+        void onSwipeUp();
+
+        void onSwipeDown();
+    }
+
+    /**
+     * Listener of single tap confirmed (click).
+     */
+    public interface SingleTapListener {
+
+        void onSingleTap(float x, float y);
+    }
+
+    private final GestureDetector gestureDetector;
+
+    private SwipeListener swipeListener;
+    private SingleTapListener singleTapListener;
+
+    public TouchView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final int swipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+        gestureDetector = new GestureDetector(
+                context, new GestureDetector.SimpleOnGestureListener() {
+
+            @Override
+            public boolean onDown(MotionEvent e) {
+                // GestureDetector onTouchEvent returns true for fling events only when their
+                // preceding down events are consumed.
+                return true;
+            }
+
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (singleTapListener != null) {
+                    singleTapListener.onSingleTap(e.getX(), e.getY());
+                }
+                return true;
+            }
+
+            @Override
+            public boolean onFling(
+                    MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
+                if (swipeListener != null) {
+                    float absX = Math.abs(velocityX);
+                    float absY = Math.abs(velocityY);
+                    float deltaX = me2.getX() - me1.getX();
+                    float deltaY = me2.getY() - me1.getY();
+                    int travelX = getWidth() / 4;
+                    int travelY = getHeight() / 4;
+                    if (velocityX > swipeThreshold && absY < absX && deltaX > travelX) {
+                        swipeListener.onSwipeRight();
+                    } else if (velocityX < -swipeThreshold && absY < absX && deltaX < -travelX) {
+                        swipeListener.onSwipeLeft();
+                    } else if (velocityY < -swipeThreshold && absX < absY && deltaY < -travelY) {
+                        swipeListener.onSwipeUp();
+                    } else if (velocityY > swipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                        swipeListener.onSwipeDown();
+                    }
+                }
+                return true;
+            }
+        });
+        gestureDetector.setIsLongpressEnabled(false);
+    }
+
+    public void setSwipeListener(SwipeListener listener) {
+        swipeListener = listener;
+    }
+
+    public void setSingleTapListener(SingleTapListener listener) {
+        singleTapListener = listener;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return isEnabled() && gestureDetector.onTouchEvent(event);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/VignetteAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/VignetteAction.java
new file mode 100644
index 0000000..6b54142
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/VignetteAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.VignetteFilter;
+
+/**
+ * An action handling vignette effect.
+ */
+public class VignetteAction extends FilterAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public VignetteAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        final VignetteFilter filter = new VignetteFilter();
+
+        scaleWheel.setOnScaleChangeListener(new ScaleWheel.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scaleWheel.setProgress(DEFAULT_SCALE);
+        scaleWheel.setVisibility(View.VISIBLE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/actions/WarmifyAction.java b/samples/PhotoEditor/src/com/android/photoeditor/actions/WarmifyAction.java
new file mode 100644
index 0000000..002e107
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/actions/WarmifyAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.actions;
+
+import android.view.ViewGroup;
+
+import com.android.photoeditor.FilterStack;
+import com.android.photoeditor.filters.WarmifyFilter;
+
+/**
+ * An action handling warmify effect.
+ */
+public class WarmifyAction extends FilterAction {
+
+    public WarmifyAction(FilterStack filterStack, ViewGroup tools) {
+        super(filterStack, tools);
+    }
+
+    @Override
+    public void onBegin() {
+        notifyFilterChanged(new WarmifyFilter(), true);
+        end();
+    }
+
+    @Override
+    public void onEnd() {
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/animation/AnimationPair.java b/samples/PhotoEditor/src/com/android/photoeditor/animation/AnimationPair.java
new file mode 100644
index 0000000..153855c
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/animation/AnimationPair.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.animation;
+
+import android.view.animation.Animation;
+
+/**
+ * A pair of animations that will be played sequentially, e.g. fade-out and then fade-in.
+ */
+public class AnimationPair {
+
+    private final Animation first;
+    private final Animation second;
+
+    AnimationPair(Animation first, Animation second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    public Animation first() {
+        return first;
+    }
+
+    public Animation second() {
+        return second;
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/animation/FadeAnimation.java b/samples/PhotoEditor/src/com/android/photoeditor/animation/FadeAnimation.java
new file mode 100644
index 0000000..45a7c33
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/animation/FadeAnimation.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.animation;
+
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+/**
+ * Fading animation that either fades in or out the view by alpha animations.
+ */
+public class FadeAnimation {
+
+    /**
+     * Gets animations that fade out and then fade in the view.
+     */
+    public static AnimationPair getFadeOutInAnimations(int duration) {
+        Animation fadeOut = new AlphaAnimation(1.0f, 0.5f);
+        Animation fadeIn = new AlphaAnimation(0.5f, 1.0f);
+        fadeOut.setDuration(duration);
+        fadeIn.setDuration(duration);
+
+        return new AnimationPair(fadeOut, fadeIn);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/animation/Rotate3DAnimation.java b/samples/PhotoEditor/src/com/android/photoeditor/animation/Rotate3DAnimation.java
new file mode 100644
index 0000000..01e5a92
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/animation/Rotate3DAnimation.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.animation;
+
+import android.graphics.Camera;
+import android.graphics.Matrix;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+
+/**
+ * Rotation animation that rotates a 2D view 90 degrees on either X or Y axis in 3D space. The
+ * rotation is performed around the view center point by its defined rotation direction and position
+ * it will start from or end to. The rotation duration can be specified as well as whether the
+ * rotation should be accelerated or decelerated in time.
+ */
+public class Rotate3DAnimation extends Animation {
+
+    public enum Rotate {
+        UP, DOWN, LEFT, RIGHT
+    }
+
+    private enum Angle {
+        FROM_DEGREES_0, TO_DEGREES_0
+    }
+
+    private static final float DEPTH_Z = 250.0f;
+    private static final float ROTATE_DEGREES = 90f;
+
+    private final Rotate rotate;
+    private final Angle angle;
+    private final int duration;
+    private final boolean accelerate;
+
+    private Camera camera;
+    private float centerX;
+    private float centerY;
+    private float fromDegrees;
+    private float toDegrees;
+
+    /**
+     * Gets animations that flip the view by 3D rotations.
+     */
+    public static AnimationPair getFlipAnimations(Rotate rotate, int duration) {
+        return new AnimationPair(
+                new Rotate3DAnimation(rotate, Angle.FROM_DEGREES_0, duration, true),
+                new Rotate3DAnimation(rotate, Angle.TO_DEGREES_0, duration, false));
+    }
+
+    private Rotate3DAnimation(Rotate rotate, Angle angle, int duration, boolean accelerate) {
+        this.rotate = rotate;
+        this.angle = angle;
+        this.duration = duration;
+        this.accelerate = accelerate;
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth, int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+        super.setDuration(duration);
+        super.setInterpolator(
+                accelerate ? new AccelerateInterpolator() : new DecelerateInterpolator());
+
+        camera = new Camera();
+        centerX = width / 2.0f;
+        centerY = height / 2.0f;
+
+        if (angle == Angle.FROM_DEGREES_0) {
+            fromDegrees = 0;
+
+            switch (rotate) {
+                case RIGHT:
+                case UP:
+                    toDegrees = ROTATE_DEGREES;
+                    break;
+
+                case LEFT:
+                case DOWN:
+                    toDegrees = -ROTATE_DEGREES;
+                    break;
+            }
+        } else if (angle == Angle.TO_DEGREES_0) {
+            toDegrees = 0;
+
+            switch (rotate) {
+                case RIGHT:
+                case UP:
+                    fromDegrees = -ROTATE_DEGREES;
+                    break;
+
+                case LEFT:
+                case DOWN:
+                    fromDegrees = ROTATE_DEGREES;
+                    break;
+            }
+        }
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        camera.save();
+
+        if (angle == Angle.FROM_DEGREES_0) {
+            camera.translate(0.0f, 0.0f, DEPTH_Z * interpolatedTime);
+        } else if (angle == Angle.TO_DEGREES_0) {
+            camera.translate(0.0f, 0.0f, DEPTH_Z * (1.0f - interpolatedTime));
+        }
+
+        float degrees = fromDegrees + ((toDegrees - fromDegrees) * interpolatedTime);
+        switch (rotate) {
+            case RIGHT:
+            case LEFT:
+                camera.rotateY(degrees);
+                break;
+
+            case UP:
+            case DOWN:
+                camera.rotateX(degrees);
+                break;
+        }
+
+        final Matrix matrix = t.getMatrix();
+        camera.getMatrix(matrix);
+        camera.restore();
+
+        matrix.preTranslate(-centerX, -centerY);
+        matrix.postTranslate(centerX, centerY);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/AutoFixFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/AutoFixFilter.java
new file mode 100644
index 0000000..027f090
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/AutoFixFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Auto-fix filter applied to the image.
+ */
+public class AutoFixFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the auto-fix level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeHEQ(src.bitmap(), dst.bitmap(), scale);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/ColorTemperatureFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/ColorTemperatureFilter.java
new file mode 100644
index 0000000..4fb00af
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/ColorTemperatureFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Color temperature filter applied to the image.
+ */
+public class ColorTemperatureFilter extends Filter {
+
+    private float colorTemperature;
+
+    /**
+     * Sets the color temperature level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setColorTemperature(float scale) {
+        this.colorTemperature = (scale - 0.5f);
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeColorTemp(src.bitmap(), dst.bitmap(), colorTemperature);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/CropFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/CropFilter.java
new file mode 100644
index 0000000..5795bbb
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/CropFilter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.RectF;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Crop filter applied to the image.
+ */
+public class CropFilter extends Filter {
+
+    private RectF bounds;
+
+    /**
+     * The coordinates used here should range from 0 to 1.
+     */
+    public void setCropBounds(RectF bounds) {
+        this.bounds = bounds;
+        validate();
+    }
+
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeCopy(src.bitmap(), dst.bitmap());
+        dst.crop((int) (src.width() * bounds.left), (int) (src.height() * bounds.top),
+                (int) (src.width() * bounds.width()), (int) (src.height() * bounds.height()));
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/CrossProcessFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/CrossProcessFilter.java
new file mode 100644
index 0000000..943f2a8
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/CrossProcessFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Cross-process filter applied to the image.
+ */
+public class CrossProcessFilter extends Filter {
+
+    public CrossProcessFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeCrossProcess(src.bitmap(), dst.bitmap());
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/DocumentaryFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/DocumentaryFilter.java
new file mode 100644
index 0000000..0a0c097
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/DocumentaryFilter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Preset "Documentary" applied to the image.
+ */
+public class DocumentaryFilter extends Filter {
+
+    public DocumentaryFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeWhiteBlack(src.bitmap(), dst.bitmap(), 0.5f, 0f);
+        ImageUtils.nativeGrayscale(dst.bitmap(), dst.bitmap(), 1.0f);
+        ImageUtils.nativeVignetting(dst.bitmap(), dst.bitmap(), 0.83f);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/DoodleFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/DoodleFilter.java
new file mode 100644
index 0000000..365a737
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/DoodleFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+
+import com.android.photoeditor.actions.ColorPath;
+import com.android.photoeditor.Photo;
+
+import java.util.Vector;
+
+/**
+ * Doodle filter applied to the image.
+ */
+public class DoodleFilter extends Filter {
+
+    private final Vector<ColorPath> doodles = new Vector<ColorPath>();
+    private final Paint paint = ColorPath.createPaint();
+    private final Canvas canvas = new Canvas();
+
+    public void addPath(int color) {
+        // Remove last empty path before adding a new one.
+        if (!doodles.isEmpty() && doodles.lastElement().path().isEmpty()) {
+            doodles.remove(doodles.size() - 1);
+        }
+        doodles.add(new ColorPath(color, new Path()));
+    }
+
+    public synchronized void updateLastPath(Path path) {
+        if (!doodles.isEmpty()) {
+            doodles.lastElement().path().set(path);
+            validate();
+        }
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeCopy(src.bitmap(), dst.bitmap());
+
+        if (!doodles.isEmpty()) {
+            canvas.setBitmap(dst.bitmap());
+            for (ColorPath doodle : doodles) {
+                doodle.draw(canvas, paint);
+            }
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/DuotoneFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/DuotoneFilter.java
new file mode 100644
index 0000000..e56f2d5
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/DuotoneFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Duotone filter applied to the image.
+ */
+public class DuotoneFilter extends Filter {
+
+    private int firstColor;
+    private int secondColor;
+
+    public void setDuotone(int firstColor, int secondColor) {
+        this.firstColor = firstColor;
+        this.secondColor = secondColor;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeDuotone(src.bitmap(), dst.bitmap(), firstColor, secondColor);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/FillLightFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/FillLightFilter.java
new file mode 100644
index 0000000..547f082
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/FillLightFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Fill-light filter applied to the image.
+ */
+public class FillLightFilter extends Filter {
+
+    private float backlight;
+
+    /**
+     * Sets the backlight level.
+     *
+     * @param backlight ranges from 0 to 1.
+     */
+    public void setBacklight(float backlight) {
+        this.backlight = backlight;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeBacklight(src.bitmap(), dst.bitmap(), backlight);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/Filter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/Filter.java
new file mode 100644
index 0000000..11afd51
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/Filter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Image filter for photo editing.
+ */
+public abstract class Filter {
+
+    private boolean isValid;
+
+    protected void validate() {
+        isValid = true;
+    }
+
+    /**
+     * Some filters, e.g. lighting filters, are initially invalid until set up with parameters while
+     * others, e.g. sepia or flip filters, are initially valid without parameters.
+     */
+    public boolean isValid() {
+        return isValid;
+    }
+
+    /**
+     * Processes the source bitmap and matrix and output the destination bitmap and matrix.
+     *
+     * @param src source photo as the input.
+     * @param dst destination photo having the same dimension as source photo as the output.
+     */
+    public abstract void process(Photo src, Photo dst);
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/FisheyeFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/FisheyeFilter.java
new file mode 100644
index 0000000..f60918a
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/FisheyeFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Fisheye filter applied to the image.
+ */
+public class FisheyeFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the fisheye distortion level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeFisheye(src.bitmap(), dst.bitmap(), 0.5f, 0.5f, scale);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/FlipFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/FlipFilter.java
new file mode 100644
index 0000000..291afa2
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/FlipFilter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Flip filter applied to the image.
+ */
+public class FlipFilter extends Filter {
+
+    private boolean flipHorizontal;
+    private boolean flipVertical;
+
+    public void setFlip(boolean flipHorizontal, boolean flipVertical) {
+        this.flipHorizontal = flipHorizontal;
+        this.flipVertical = flipVertical;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        if (flipHorizontal && flipVertical) {
+            ImageUtils.nativeFlipBoth(src.bitmap(), dst.bitmap());
+        } else if (flipHorizontal) {
+            ImageUtils.nativeFlipHorizontal(src.bitmap(), dst.bitmap());
+        } else if (flipVertical){
+            ImageUtils.nativeFlipVertical(src.bitmap(), dst.bitmap());
+        } else {
+            ImageUtils.nativeCopy(src.bitmap(), dst.bitmap());
+        }
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/GrainFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/GrainFilter.java
new file mode 100644
index 0000000..8ee81d2
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/GrainFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Film grain filter applied to the image.
+ */
+public class GrainFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Set the grain noise level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeGrain(src.bitmap(), dst.bitmap(), scale);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/GrayscaleFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/GrayscaleFilter.java
new file mode 100644
index 0000000..164aaa0
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/GrayscaleFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Grayscale filter applied to the image.
+ */
+public class GrayscaleFilter extends Filter {
+
+    public GrayscaleFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeGrayscale(src.bitmap(), dst.bitmap(), 1.0f);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/HighlightFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/HighlightFilter.java
new file mode 100644
index 0000000..22efbe1
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/HighlightFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Highlight filter applied to the image.
+ */
+public class HighlightFilter extends Filter {
+
+    private float white;
+
+    /**
+     * Sets the highlight level.
+     *
+     * @param highlight ranges from 0 to 1.
+     */
+    public void setHighlight(float highlight) {
+        white = 1f - highlight * 0.5f;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeWhiteBlack(src.bitmap(), dst.bitmap(), white, 0f);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/ImageUtils.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/ImageUtils.java
new file mode 100644
index 0000000..e37a30f
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/ImageUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.PointF;
+
+
+/**
+ * Image utilities that calls to JNI methods for image processing.
+ */
+public class ImageUtils {
+
+    static {
+        System.loadLibrary("jni_photoeditor");
+    }
+  
+    public static native boolean init(byte[] pgm, int pgmLength);
+   
+    public static native void nativeBacklight(Bitmap src, Bitmap dst, float backlight);
+    public static native void nativeBlur(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeColorTemp(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeCopy(Bitmap src, Bitmap dst);
+    public static native void nativeCrossProcess(Bitmap src, Bitmap dst);
+    public static native void nativeDuotone(Bitmap src, Bitmap dst, int firstColor,
+            int secondColor);
+    public static native void nativeFisheye(Bitmap src, Bitmap dst, float focusX,
+            float focusY, float scale);
+    public static native void nativeFlipBoth(Bitmap src, Bitmap dst);
+    public static native void nativeFlipHorizontal(Bitmap src, Bitmap dst);
+    public static native void nativeFlipVertical(Bitmap src, Bitmap dst);
+    public static native void nativeGrain(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeGrayscale(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeHEQ(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeNegative(Bitmap src, Bitmap dst);
+    public static native void nativeQuantize(Bitmap src, Bitmap dst);
+    public static native void nativeRedEye(Bitmap src, Bitmap dst, PointF[] redeyes,
+            float radius, float intensity);
+    public static native void nativeSaturation(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeSepia(Bitmap src, Bitmap dst);
+    public static native void nativeSharpen(Bitmap src, Bitmap dst, float scale);
+    public static native void nativeTint(Bitmap src, Bitmap dst, int tint);
+    public static native void nativeVignetting(Bitmap src, Bitmap dst, float range);
+    public static native void nativeWarmify(Bitmap src, Bitmap dst);
+    public static native void nativeWhiteBlack(Bitmap src, Bitmap dst, float white, float black);
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/LomoishFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/LomoishFilter.java
new file mode 100644
index 0000000..1028081
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/LomoishFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Preset "Lomo-ish" applied to the image.
+ */
+public class LomoishFilter extends Filter {
+
+    public LomoishFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeBlur(src.bitmap(), dst.bitmap(), 0.3f);
+        ImageUtils.nativeCrossProcess(dst.bitmap(), dst.bitmap());
+        ImageUtils.nativeWhiteBlack(dst.bitmap(), dst.bitmap(), 0.8f, 0.15f);
+        ImageUtils.nativeVignetting(dst.bitmap(), dst.bitmap(), 0.73f);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/NegativeFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/NegativeFilter.java
new file mode 100644
index 0000000..fda1862
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/NegativeFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Negative filter applied to the image.
+ */
+public class NegativeFilter extends Filter {
+
+    public NegativeFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeNegative(src.bitmap(), dst.bitmap());
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/PosterizeFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/PosterizeFilter.java
new file mode 100644
index 0000000..4001a90
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/PosterizeFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * "Posterize" filter applied to the image.
+ */
+public class PosterizeFilter extends Filter {
+
+    public PosterizeFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeQuantize(src.bitmap(), dst.bitmap());
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/RedEyeFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/RedEyeFilter.java
new file mode 100644
index 0000000..0965c61
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/RedEyeFilter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.PointF;
+
+import com.android.photoeditor.Photo;
+
+import java.util.Vector;
+
+/**
+ * Red-eye removal filter applied to the image.
+ */
+public class RedEyeFilter extends Filter {
+
+    private static final float RADIUS_RATIO = 0.06f;
+    private static final float MIN_RADIUS = 10.0f;
+    private static final float DEFAULT_RED_INTENSITY = 1.30f; // an empirical value
+
+    private final Vector<PointF> redeyePositions = new Vector<PointF>();
+
+    public void addRedEyePosition(PointF point) {
+        redeyePositions.add(point);
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        float radius = Math.max(MIN_RADIUS, RADIUS_RATIO * Math.min(src.width(), src.height()));
+
+        PointF[] a = new PointF[redeyePositions.size()];
+        ImageUtils.nativeRedEye(src.bitmap(), dst.bitmap(),
+                redeyePositions.toArray(a), radius, DEFAULT_RED_INTENSITY);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/RotateFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/RotateFilter.java
new file mode 100644
index 0000000..5152aca
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/RotateFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.Matrix;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Rotate filter applied to the image.
+ */
+public class RotateFilter extends Filter {
+
+    private final Matrix matrix = new Matrix();
+
+    public void setAngle(float degrees) {
+        matrix.setRotate(degrees);
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeCopy(src.bitmap(), dst.bitmap());
+        dst.transform(matrix);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/SaturationFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/SaturationFilter.java
new file mode 100644
index 0000000..6590670
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/SaturationFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Saturation filter applied to the image.
+ */
+public class SaturationFilter extends Filter {
+
+    private float saturation;
+
+    /**
+     * Sets the saturation level.
+     *
+     * @param saturation ranges from 0 to 1.
+     */
+    public void setSaturation(float saturation) {
+        this.saturation = (saturation - 0.5f) * 2;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeSaturation(src.bitmap(), dst.bitmap(), saturation);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/SepiaFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/SepiaFilter.java
new file mode 100644
index 0000000..696f578
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/SepiaFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Sepia filter applied to the image.
+ */
+public class SepiaFilter extends Filter {
+
+    public SepiaFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeSepia(src.bitmap(), dst.bitmap());
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/ShadowFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/ShadowFilter.java
new file mode 100644
index 0000000..5008e6a
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/ShadowFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Shadow filter applied to the image.
+ */
+public class ShadowFilter extends Filter {
+
+    private float black;
+
+    /**
+     * Sets the shadow blackness level.
+     *
+     * @param shadow ranges from 0 to 1.
+     */
+    public void setShadow(float shadow) {
+        black = shadow * 0.5f;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeWhiteBlack(src.bitmap(), dst.bitmap(), 1f, black);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/SharpenFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/SharpenFilter.java
new file mode 100644
index 0000000..7d3aa0d
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/SharpenFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Sharpen filter applied to the image.
+ */
+public class SharpenFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the sharpen level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setSharpen(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeSharpen(src.bitmap(), dst.bitmap(), scale);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/SoftFocusFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/SoftFocusFilter.java
new file mode 100644
index 0000000..d2e41a5
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/SoftFocusFilter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Soft focus filter applied to the image.
+ */
+public class SoftFocusFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the soft focus level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setSoftFocus(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeBlur(src.bitmap(), dst.bitmap(), scale);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/StraightenFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/StraightenFilter.java
new file mode 100644
index 0000000..8f265bd
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/StraightenFilter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+import com.android.photoeditor.Photo;
+import com.android.photoeditor.RectUtils;
+
+/**
+ * Straighten filter applied to the image.
+ */
+public class StraightenFilter extends Filter {
+
+    private final RectF bounds = new RectF();
+    private final Matrix matrix = new Matrix();
+    private final Canvas canvas = new Canvas();
+    private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+
+    private float angle;
+
+    public void setAngle(float degrees) {
+        this.angle = degrees;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        bounds.set(0, 0, src.width(), src.height());
+        RectUtils.getStraightenMatrix(bounds, angle, matrix);
+        matrix.mapRect(bounds);
+        matrix.postTranslate((src.width() - bounds.width()) / 2,
+                (src.height() - bounds.height()) / 2);
+        canvas.setBitmap(dst.bitmap());
+        canvas.drawBitmap(src.bitmap(), matrix, paint);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/TintFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/TintFilter.java
new file mode 100644
index 0000000..ef06fee
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/TintFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Tint filter applied to the image.
+ */
+public class TintFilter extends Filter {
+
+    private int tint;
+
+    public void setTint(int color) {
+        tint = color;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeTint(src.bitmap(), dst.bitmap(), tint);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/VignetteFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/VignetteFilter.java
new file mode 100644
index 0000000..3d5b566
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/VignetteFilter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Vignette filter applied to the image.
+ */
+public class VignetteFilter extends Filter {
+
+    private float range;
+
+    /**
+     * Sets the vignette range scale.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
+        // which means no vignette at all because the luminousity difference is
+        // less than 1/256 and will cause nothing.
+        range = 1.30f - (float) Math.sqrt(scale) * 0.7f;
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeVignetting(src.bitmap(), dst.bitmap(), range);
+    }
+}
diff --git a/samples/PhotoEditor/src/com/android/photoeditor/filters/WarmifyFilter.java b/samples/PhotoEditor/src/com/android/photoeditor/filters/WarmifyFilter.java
new file mode 100644
index 0000000..dc745a0
--- /dev/null
+++ b/samples/PhotoEditor/src/com/android/photoeditor/filters/WarmifyFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photoeditor.filters;
+
+import com.android.photoeditor.Photo;
+
+/**
+ * Warmify filter applied to the image.
+ */
+public class WarmifyFilter extends Filter {
+
+    public WarmifyFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(Photo src, Photo dst) {
+        ImageUtils.nativeWarmify(src.bitmap(), dst.bitmap());
+    }
+}
diff --git a/samples/quake/res/drawable/app_quake.png b/samples/quake/res/drawable/app_quake.png
new file mode 100644
index 0000000..333b723
--- /dev/null
+++ b/samples/quake/res/drawable/app_quake.png
Binary files differ
diff --git a/samples/quake/res/layout/downloader.xml b/samples/quake/res/layout/downloader.xml
new file mode 100644
index 0000000..968c5f2
--- /dev/null
+++ b/samples/quake/res/layout/downloader.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+      <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+          <TextView android:id="@+id/customText"
+              android:layout_width="0dip"
+              android:layout_weight="1"
+              android:layout_height="wrap_content"
+              android:padding="3dip"
+              android:textAppearance="?android:attr/textAppearanceLarge" />
+      </LinearLayout>
+      <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+          <TextView android:text="@string/download_activity_progress"
+              android:layout_width="0dip"
+              android:layout_weight="1"
+              android:layout_height="wrap_content"
+              android:padding="3dip"
+              android:gravity="right"
+              android:textAppearance="?android:attr/textAppearanceMedium" />
+          <TextView android:id="@+id/progress"
+              android:layout_width="0dip"
+              android:layout_weight="1"
+              android:layout_height="wrap_content"
+              android:padding="3dip"
+              android:textAppearance="?android:attr/textAppearanceMedium" />
+      </LinearLayout>
+
+      <LinearLayout
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content">
+          <TextView android:text="@string/download_activity_time_remaining"
+              android:layout_width="0dip"
+              android:layout_weight="1"
+              android:layout_height="wrap_content"
+              android:padding="3dip"
+              android:gravity="right"
+              android:textAppearance="?android:attr/textAppearanceMedium" />
+          <TextView android:id="@+id/time_remaining"
+              android:layout_width="0dip"
+              android:layout_weight="1"
+              android:layout_height="wrap_content"
+              android:padding="3dip"
+              android:textAppearance="?android:attr/textAppearanceMedium" />
+      </LinearLayout>
+
+  <Button android:id="@+id/cancel"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:layout_marginTop="5dip"
+    android:text="@string/download_activity_cancel" />
+</LinearLayout>
diff --git a/samples/quake/res/layout/downloader_title.xml b/samples/quake/res/layout/downloader_title.xml
new file mode 100644
index 0000000..eae5d33
--- /dev/null
+++ b/samples/quake/res/layout/downloader_title.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+    * Copyright (C) 2008 The Android Open Source Project
+    *
+    * Licensed under the Apache License, Version 2.0 (the "License");
+    * you may not use this file except in compliance with the License.
+    * You may obtain a copy of the License at
+    *
+    *      http://www.apache.org/licenses/LICENSE-2.0
+    *
+    * Unless required by applicable law or agreed to in writing, software
+    * distributed under the License is distributed on an "AS IS" BASIS,
+    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    * See the License for the specific language governing permissions and
+    * limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/screen"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/windowTitleSize"
+    android:orientation="horizontal">
+
+    <TextView android:id="@+id/title_text"
+        android:gravity="center_vertical"
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:paddingLeft="5dip"
+        style="?android:attr/windowTitleStyle"
+        android:singleLine="true"
+        android:layout_weight="1"
+        android:ellipsize="end" />
+
+    <LinearLayout android:id="@+id/loading_indicator"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content">
+
+        <TextView android:text="@string/download_activity_title"
+            android:gravity="center_vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:paddingRight="5dip"
+            style="?android:attr/windowTitleStyle" />
+
+        <ProgressBar android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleSmallTitle"
+            android:layout_gravity="center_vertical"
+            android:paddingRight="5dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/samples/quake/res/menu/menu.xml b/samples/quake/res/menu/menu.xml
new file mode 100644
index 0000000..4a0dfe3
--- /dev/null
+++ b/samples/quake/res/menu/menu.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:id="@+id/switch_mode"
+  android:title="@string/switch_mode" />
+</menu>
+
diff --git a/samples/quake/res/values/strings.xml b/samples/quake/res/values/strings.xml
new file mode 100644
index 0000000..3c9ee40
--- /dev/null
+++ b/samples/quake/res/values/strings.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Quake/res/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <!-- Simple strings. -->
+    <string name="quake_activity">Quake</string>
+    <!-- Displayed while copying Quake data to storage.  CHAR LIMIT=NONE -->
+    <string name="quake_customDownloadText">Please wait while the Quake data files are copied.</string>
+    <string name="download_activity_title">Downloading</string>
+    <string name="download_activity_cancel">Cancel Download</string>
+    <string name="download_activity_progress">Progress:</string>
+    <string name="download_activity_time_remaining">Time Remaining:</string>
+    <string name="download_activity_time_remaining_unknown">unknown</string>
+    <string name="download_activity_time_remaining_minutes">minutes</string>
+    <string name="download_activity_time_remaining_seconds">seconds</string>
+    <string name="download_activity_time_remaining_hours">hours</string>
+    <string name="download_activity_time_remaining_days">days</string>
+    <string name="download_activity_download_stopped">Download stopped</string>
+    <string name="download_activity_retry">Retry</string>
+    <string name="download_activity_quit">Quit</string>
+    <string name="switch_mode">Switch Mode</string> 
+</resources>
+
diff --git a/toolchains/llvm/setup.mk b/toolchains/llvm/setup.mk
index e9832f7..5a3fbc5 100644
--- a/toolchains/llvm/setup.mk
+++ b/toolchains/llvm/setup.mk
@@ -11,7 +11,8 @@
 TARGET_C_INCLUDES := $(GDK_PLATFORMS_ROOT)/android-portable/arch-llvm/usr/include
 
 # Workaround before the required headers are in the above dir.
-TARGET_C_INCLUDES += $(NDK_ROOT)/platforms/android-9/arch-arm/usr/include/ \
+TARGET_C_INCLUDES += $(NDK_ROOT)/platforms/android-9/arch-arm/usr/include \
+                     $(NDK_ROOT)/sources/cxx-stl/system/include \
                      $(NDK_ROOT)/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/lib/gcc/arm-linux-androideabi/4.4.3/include
 
 TARGET_CC       := $(OUT)/../../../host/linux-x86/bin/clang