Merge "Use right arrow for the commit indicator" into lmp-dev
diff --git a/java/res/anim/key_preview_dismiss_holo.xml b/java/res/anim/key_preview_dismiss_holo.xml
new file mode 100644
index 0000000..0bf7254
--- /dev/null
+++ b/java/res/anim/key_preview_dismiss_holo.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:propertyName="scaleX"
+        android:duration="53"
+        android:valueFrom="1.00"
+        android:valueTo="0.94" />
+    <objectAnimator
+        android:propertyName="scaleY"
+        android:duration="53"
+        android:valueFrom="1.00"
+        android:valueTo="0.94" />
+</set>
diff --git a/java/res/anim/key_preview_dismiss_lxx.xml b/java/res/anim/key_preview_dismiss_lxx.xml
new file mode 100644
index 0000000..326e534
--- /dev/null
+++ b/java/res/anim/key_preview_dismiss_lxx.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:propertyName="scaleX"
+        android:duration="53"
+        android:valueFrom="1.00"
+        android:valueTo="1.00" />
+    <objectAnimator
+        android:propertyName="scaleY"
+        android:duration="53"
+        android:valueFrom="1.00"
+        android:valueTo="0.94" />
+</set>
diff --git a/java/res/anim/key_preview_show_up_holo.xml b/java/res/anim/key_preview_show_up_holo.xml
new file mode 100644
index 0000000..ad2e413
--- /dev/null
+++ b/java/res/anim/key_preview_show_up_holo.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:propertyName="scaleX"
+        android:duration="17"
+        android:valueFrom="0.98"
+        android:valueTo="1.00" />
+    <objectAnimator
+        android:propertyName="scaleY"
+        android:duration="17"
+        android:valueFrom="0.98"
+        android:valueTo="1.00" />
+</set>
diff --git a/java/res/anim/key_preview_show_up_lxx.xml b/java/res/anim/key_preview_show_up_lxx.xml
new file mode 100644
index 0000000..f500349
--- /dev/null
+++ b/java/res/anim/key_preview_show_up_lxx.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:propertyName="scaleX"
+        android:duration="17"
+        android:valueFrom="1.00"
+        android:valueTo="1.00" />
+    <objectAnimator
+        android:propertyName="scaleY"
+        android:duration="17"
+        android:valueFrom="0.98"
+        android:valueTo="1.00" />
+</set>
diff --git a/java/res/drawable/btn_keyboard_key_popup_lxx_light.xml b/java/res/drawable/btn_keyboard_key_popup_lxx_light.xml
new file mode 100644
index 0000000..d6cd2b8
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_popup_lxx_light.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_popup_selected_lxx_light" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index e89912a..c756f8c 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -113,6 +113,10 @@
         <!-- TODO: consolidate key preview linger timeout with the key preview animation parameters. -->
         <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
         <attr name="keyPreviewLingerTimeout" format="integer" />
+        <!-- Key preview show up animator -->
+        <attr name="keyPreviewShowUpAnimator" format="reference" />
+        <!-- Key preview dismiss animator -->
+        <attr name="keyPreviewDismissAnimator" format="reference" />
         <!-- Layout resource for more keys keyboard -->
         <attr name="moreKeysKeyboardLayout" format="reference" />
         <attr name="backgroundDimAlpha" format="integer" />
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index b3e58e9..120b648 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -331,14 +331,20 @@
     <string name="prefs_keypress_vibration_duration_settings">Keypress vibration duration</string>
     <!-- Title of the settings for keypress sound volume [CHAR LIMIT=35] -->
     <string name="prefs_keypress_sound_volume_settings">Keypress sound volume</string>
+    <!-- Title of the settings for customize key popup animation parameters [CHAR LIMIT=35] -->
+    <string name="prefs_customize_key_preview_animation">Customize key preview animation</string>
     <!-- Title of the settings for key popup show up animation duration (in milliseconds) [CHAR LIMIT=35] -->
     <string name="prefs_key_popup_show_up_duration_settings" translatable="false">Key popup show up duration</string>
     <!-- Title of the settings for key popup dismiss animation duration (in milliseconds) [CHAR LIMIT=35] -->
     <string name="prefs_key_popup_dismiss_duration_settings" translatable="false">Key popup dismiss duration</string>
-    <!-- Title of the settings for key popup show up animation start scale (in percentile) [CHAR LIMIT=35] -->
-    <string name="prefs_key_popup_show_up_start_scale_settings" translatable="false">Key popup show up start scale</string>
-    <!-- Title of the settings for key popup dismiss animation end scale (in percentile) [CHAR LIMIT=35] -->
-    <string name="prefs_key_popup_dismiss_end_scale_settings" translatable="false">Key popup dismiss end scale</string>
+    <!-- Title of the settings for key popup show up animation start X-scale (in percentile) [CHAR LIMIT=35] -->
+    <string name="prefs_key_popup_show_up_start_x_scale_settings" translatable="false">Key popup show up start X scale</string>
+    <!-- Title of the settings for key popup show up animation start Y-scale (in percentile) [CHAR LIMIT=35] -->
+    <string name="prefs_key_popup_show_up_start_y_scale_settings" translatable="false">Key popup show up start Y scale</string>
+    <!-- Title of the settings for key popup dismiss animation end X-scale (in percentile) [CHAR LIMIT=35] -->
+    <string name="prefs_key_popup_dismiss_end_x_scale_settings" translatable="false">Key popup dismiss end X scale</string>
+    <!-- Title of the settings for key popup dismiss animation end Y-scale (in percentile) [CHAR LIMIT=35] -->
+    <string name="prefs_key_popup_dismiss_end_y_scale_settings" translatable="false">Key popup dismiss end Y scale</string>
     <!-- Title of the settings for reading an external dictionary file -->
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 6fddcb9..051489e 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -59,6 +59,8 @@
     >
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_ics</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
+        <item name="keyPreviewShowUpAnimator">@anim/key_preview_show_up_holo</item>
+        <item name="keyPreviewDismissAnimator">@anim/key_preview_dismiss_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_ics</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
         <item name="gestureTrailColor">@color/highlight_color_ics</item>
diff --git a/java/res/values/themes-klp.xml b/java/res/values/themes-klp.xml
index c9b8331..a853ed9 100644
--- a/java/res/values/themes-klp.xml
+++ b/java/res/values/themes-klp.xml
@@ -59,6 +59,8 @@
     >
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_klp</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
+        <item name="keyPreviewShowUpAnimator">@anim/key_preview_show_up_holo</item>
+        <item name="keyPreviewDismissAnimator">@anim/key_preview_dismiss_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_klp</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
         <item name="gestureTrailColor">@color/highlight_color_klp</item>
diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml
index 6afbd9b..2aaee13 100644
--- a/java/res/values/themes-lxx-dark.xml
+++ b/java/res/values/themes-lxx-dark.xml
@@ -59,6 +59,8 @@
     >
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_lxx_dark</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
+        <item name="keyPreviewShowUpAnimator">@anim/key_preview_show_up_lxx</item>
+        <item name="keyPreviewDismissAnimator">@anim/key_preview_dismiss_lxx</item>
         <item name="gestureFloatingPreviewTextColor">@color/auto_correct_color_lxx_dark</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_lxx_dark</item>
         <item name="gestureTrailColor">@color/gesture_trail_color_lxx_dark</item>
diff --git a/java/res/values/themes-lxx-light.xml b/java/res/values/themes-lxx-light.xml
index b3ced80..e7a6f58 100644
--- a/java/res/values/themes-lxx-light.xml
+++ b/java/res/values/themes-lxx-light.xml
@@ -59,6 +59,8 @@
     >
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_lxx_light</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
+        <item name="keyPreviewShowUpAnimator">@anim/key_preview_show_up_lxx</item>
+        <item name="keyPreviewDismissAnimator">@anim/key_preview_dismiss_lxx</item>
         <item name="gestureFloatingPreviewTextColor">@color/auto_correct_color_lxx_light</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_lxx_light</item>
         <item name="gestureTrailColor">@color/gesture_trail_color_lxx_light</item>
@@ -98,8 +100,7 @@
         parent="KeyboardView.LXX_Light"
     >
         <item name="android:background">@drawable/keyboard_popup_panel_background_lxx_light</item>
-        <!-- Reuse KLP key background -->
-        <item name="keyBackground">@drawable/btn_keyboard_key_popup_klp</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_popup_lxx_light</item>
         <item name="keyTypeface">normal</item>
         <item name="verticalCorrection">@dimen/config_more_keys_keyboard_vertical_correction_holo</item>
     </style>
diff --git a/java/res/xml/prefs_screen_debug.xml b/java/res/xml/prefs_screen_debug.xml
index 965369a..e0f3501 100644
--- a/java/res/xml/prefs_screen_debug.xml
+++ b/java/res/xml/prefs_screen_debug.xml
@@ -52,19 +52,38 @@
         latin:minValue="@integer/config_min_longpress_timeout"
         latin:maxValue="@integer/config_max_longpress_timeout"
         latin:stepValue="@integer/config_longpress_timeout_step" />
+    <CheckBoxPreference
+        android:key="pref_has_custom_key_preview_animation_params"
+        android:title="@string/prefs_customize_key_preview_animation"
+        android:defaultValue="false"
+        android:persistent="true" />
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
-        android:key="pref_key_preview_show_up_start_scale"
-        android:title="@string/prefs_key_popup_show_up_start_scale_settings"
+        android:dependency="pref_customize_key_preview_animation"
+        android:key="pref_key_preview_show_up_start_x_scale"
+        android:title="@string/prefs_key_popup_show_up_start_x_scale_settings"
         latin:maxValue="100" /> <!-- percent -->
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
-        android:key="pref_key_preview_dismiss_end_scale"
-        android:title="@string/prefs_key_popup_dismiss_end_scale_settings"
+        android:dependency="pref_customize_key_preview_animation"
+        android:key="pref_key_preview_show_up_start_y_scale"
+        android:title="@string/prefs_key_popup_show_up_start_y_scale_settings"
         latin:maxValue="100" /> <!-- percent -->
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+        android:dependency="pref_customize_key_preview_animation"
+        android:key="pref_key_preview_dismiss_end_x_scale"
+        android:title="@string/prefs_key_popup_dismiss_end_x_scale_settings"
+        latin:maxValue="100" /> <!-- percent -->
+    <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+        android:dependency="pref_customize_key_preview_animation"
+        android:key="pref_key_preview_dismiss_end_y_scale"
+        android:title="@string/prefs_key_popup_dismiss_end_y_scale_settings"
+        latin:maxValue="100" /> <!-- percent -->
+    <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+        android:dependency="pref_customize_key_preview_animation"
         android:key="pref_key_preview_show_up_duration"
         android:title="@string/prefs_key_popup_show_up_duration_settings"
         latin:maxValue="100" /> <!-- milliseconds -->
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+        android:dependency="pref_customize_key_preview_animation"
         android:key="pref_key_preview_dismiss_duration"
         android:title="@string/prefs_key_popup_dismiss_duration_settings"
         latin:maxValue="100" /> <!-- milliseconds -->
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index e952f02..ec089f7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -153,9 +153,12 @@
                 mCurrentSettingsValues.mKeyPreviewPopupOn,
                 mCurrentSettingsValues.mKeyPreviewPopupDismissDelay);
         keyboardView.setKeyPreviewAnimationParams(
-                mCurrentSettingsValues.mKeyPreviewShowUpStartScale,
+                mCurrentSettingsValues.mHasCustomKeyPreviewAnimationParams,
+                mCurrentSettingsValues.mKeyPreviewShowUpStartXScale,
+                mCurrentSettingsValues.mKeyPreviewShowUpStartYScale,
                 mCurrentSettingsValues.mKeyPreviewShowUpDuration,
-                mCurrentSettingsValues.mKeyPreviewDismissEndScale,
+                mCurrentSettingsValues.mKeyPreviewDismissEndXScale,
+                mCurrentSettingsValues.mKeyPreviewDismissEndYScale,
                 mCurrentSettingsValues.mKeyPreviewDismissDuration);
         keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
         final boolean subtypeChanged = (oldKeyboard == null)
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 1ef53a6..c73f084 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -86,6 +86,8 @@
  * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
  * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
  * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewShowUpAnimator
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewDismissAnimator
  * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
  * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
@@ -391,20 +393,34 @@
     }
 
     /**
-     * Enables or disables the key feedback popup. This is a popup that shows a magnified
+     * Enables or disables the key preview popup. This is a popup that shows a magnified
      * version of the depressed key. By default the preview is enabled.
      * @param previewEnabled whether or not to enable the key feedback preview
      * @param delay the delay after which the preview is dismissed
-     * @see #isKeyPreviewPopupEnabled()
      */
     public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
         mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
     }
 
-    public void setKeyPreviewAnimationParams(final float showUpStartScale, final int showUpDuration,
-            final float dismissEndScale, final int dismissDuration) {
-        mKeyPreviewDrawParams.setAnimationParams(
-                showUpStartScale, showUpDuration, dismissEndScale, dismissDuration);
+    /**
+     * Enables or disables the key preview popup animations and set animations' parameters.
+     *
+     * @param hasCustomAnimationParams false to use the default key preview popup animations
+     *   specified by keyPreviewShowUpAnimator and keyPreviewDismissAnimator attributes.
+     *   true to override the default animations with the specified parameters.
+     * @param showUpStartXScale from this x-scale the show up animation will start.
+     * @param showUpStartYScale from this y-scale the show up animation will start.
+     * @param showUpDuration the duration of the show up animation in milliseconds.
+     * @param dismissEndXScale to this x-scale the dismiss animation will end.
+     * @param dismissEndYScale to this y-scale the dismiss animation will end.
+     * @param dismissDuration the duration of the dismiss animation in milliseconds.
+     */
+    public void setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams,
+            final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration,
+            final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration) {
+        mKeyPreviewDrawParams.setAnimationParams(hasCustomAnimationParams,
+                showUpStartXScale, showUpStartYScale, showUpDuration,
+                dismissEndXScale, dismissEndYScale, dismissDuration);
     }
 
     private void locatePreviewPlacerView() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index cd29c8d..5005b7d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -18,13 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
@@ -89,9 +85,9 @@
         }
         final Object tag = keyPreviewView.getTag();
         if (withAnimation) {
-            if (tag instanceof KeyPreviewAnimations) {
-                final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
-                animation.startDismiss();
+            if (tag instanceof KeyPreviewAnimators) {
+                final KeyPreviewAnimators animators = (KeyPreviewAnimators)tag;
+                animators.startDismiss();
                 return;
             }
         }
@@ -161,87 +157,60 @@
         }
 
         // Show preview with animation.
-        final Animator showUpAnimation = createShowUpAniation(key, keyPreviewView);
-        final Animator dismissAnimation = createDismissAnimation(key, keyPreviewView);
-        final KeyPreviewAnimations animation = new KeyPreviewAnimations(
-                showUpAnimation, dismissAnimation);
-        keyPreviewView.setTag(animation);
-        animation.startShowUp();
+        final Animator showUpAnimator = createShowUpAnimator(key, keyPreviewView);
+        final Animator dismissAnimator = createDismissAnimator(key, keyPreviewView);
+        final KeyPreviewAnimators animators = new KeyPreviewAnimators(
+                showUpAnimator, dismissAnimator);
+        keyPreviewView.setTag(animators);
+        animators.startShowUp();
     }
 
-    private static final float KEY_PREVIEW_SHOW_UP_END_SCALE = 1.0f;
-    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
-            new AccelerateInterpolator();
-    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
-            new DecelerateInterpolator();
-
-    private Animator createShowUpAniation(final Key key, final KeyPreviewView keyPreviewView) {
-        // TODO: Optimization for no scale animation and no duration.
-        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                keyPreviewView, View.SCALE_X, mParams.getShowUpStartScale(),
-                KEY_PREVIEW_SHOW_UP_END_SCALE);
-        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                keyPreviewView, View.SCALE_Y, mParams.getShowUpStartScale(),
-                KEY_PREVIEW_SHOW_UP_END_SCALE);
-        final AnimatorSet showUpAnimation = new AnimatorSet();
-        showUpAnimation.play(scaleXAnimation).with(scaleYAnimation);
-        showUpAnimation.setDuration(mParams.getShowUpDuration());
-        showUpAnimation.setInterpolator(DECELERATE_INTERPOLATOR);
-        showUpAnimation.addListener(new AnimatorListenerAdapter() {
+    public Animator createShowUpAnimator(final Key key, final KeyPreviewView keyPreviewView) {
+        final Animator animator = mParams.createShowUpAnimator(keyPreviewView);
+        animator.addListener(new AnimatorListenerAdapter() {
             @Override
-            public void onAnimationStart(final Animator animation) {
+            public void onAnimationStart(final Animator animator) {
                 showKeyPreview(key, keyPreviewView, false /* withAnimation */);
             }
         });
-        return showUpAnimation;
+        return animator;
     }
 
-    private Animator createDismissAnimation(final Key key, final KeyPreviewView keyPreviewView) {
-        // TODO: Optimization for no scale animation and no duration.
-        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                keyPreviewView, View.SCALE_X, mParams.getDismissEndScale());
-        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                keyPreviewView, View.SCALE_Y, mParams.getDismissEndScale());
-        final AnimatorSet dismissAnimation = new AnimatorSet();
-        dismissAnimation.play(scaleXAnimation).with(scaleYAnimation);
-        final int dismissDuration = Math.min(
-                mParams.getDismissDuration(), mParams.getLingerTimeout());
-        dismissAnimation.setDuration(dismissDuration);
-        dismissAnimation.setInterpolator(ACCELERATE_INTERPOLATOR);
-        dismissAnimation.addListener(new AnimatorListenerAdapter() {
+    private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) {
+        final Animator animator = mParams.createDismissAnimator(keyPreviewView);
+        animator.addListener(new AnimatorListenerAdapter() {
             @Override
-            public void onAnimationEnd(final Animator animation) {
+            public void onAnimationEnd(final Animator animator) {
                 dismissKeyPreview(key, false /* withAnimation */);
             }
         });
-        return dismissAnimation;
+        return animator;
     }
 
-    private static class KeyPreviewAnimations extends AnimatorListenerAdapter {
-        private final Animator mShowUpAnimation;
-        private final Animator mDismissAnimation;
+    private static class KeyPreviewAnimators extends AnimatorListenerAdapter {
+        private final Animator mShowUpAnimator;
+        private final Animator mDismissAnimator;
 
-        public KeyPreviewAnimations(final Animator showUpAnimation,
-                final Animator dismissAnimation) {
-            mShowUpAnimation = showUpAnimation;
-            mDismissAnimation = dismissAnimation;
+        public KeyPreviewAnimators(final Animator showUpAnimator, final Animator dismissAnimator) {
+            mShowUpAnimator = showUpAnimator;
+            mDismissAnimator = dismissAnimator;
         }
 
         public void startShowUp() {
-            mShowUpAnimation.start();
+            mShowUpAnimator.start();
         }
 
         public void startDismiss() {
-            if (mShowUpAnimation.isRunning()) {
-                mShowUpAnimation.addListener(this);
+            if (mShowUpAnimator.isRunning()) {
+                mShowUpAnimator.addListener(this);
                 return;
             }
-            mDismissAnimation.start();
+            mDismissAnimator.start();
         }
 
         @Override
-        public void onAnimationEnd(final Animator animation) {
-            mDismissAnimation.start();
+        public void onAnimationEnd(final Animator animator) {
+            mDismissAnimator.start();
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
index 68c9831..5ed39f9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -16,8 +16,14 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.content.res.TypedArray;
 import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 
 import com.android.inputmethod.latin.R;
 
@@ -26,10 +32,15 @@
     public final int mPreviewOffset;
     public final int mPreviewHeight;
     public final int mPreviewBackgroundResId;
+    private final int mShowUpAnimatorResId;
+    private final int mDismissAnimatorResId;
+    private boolean mHasCustomAnimationParams;
     private int mShowUpDuration;
     private int mDismissDuration;
-    private float mShowUpStartScale;
-    private float mDismissEndScale;
+    private float mShowUpStartXScale;
+    private float mShowUpStartYScale;
+    private float mDismissEndXScale;
+    private float mDismissEndYScale;
     private int mLingerTimeout;
     private boolean mShowPopup = true;
 
@@ -67,6 +78,10 @@
                 R.styleable.MainKeyboardView_keyPreviewBackground, 0);
         mLingerTimeout = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
+        mShowUpAnimatorResId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewShowUpAnimator, 0);
+        mDismissAnimatorResId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewDismissAnimator, 0);
     }
 
     public void setVisibleOffset(final int previewVisibleOffset) {
@@ -112,27 +127,62 @@
         return mLingerTimeout;
     }
 
-    public void setAnimationParams(final float showUpStartScale, final int showUpDuration,
-            final float dismissEndScale, final int dismissDuration) {
-        mShowUpStartScale = showUpStartScale;
+    public void setAnimationParams(final boolean hasCustomAnimationParams,
+            final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration,
+            final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration) {
+        mHasCustomAnimationParams = hasCustomAnimationParams;
+        mShowUpStartXScale = showUpStartXScale;
+        mShowUpStartYScale = showUpStartYScale;
         mShowUpDuration = showUpDuration;
-        mDismissEndScale = dismissEndScale;
+        mDismissEndXScale = dismissEndXScale;
+        mDismissEndYScale = dismissEndYScale;
         mDismissDuration = dismissDuration;
     }
 
-    public float getShowUpStartScale() {
-        return mShowUpStartScale;
+    private static final float KEY_PREVIEW_SHOW_UP_END_SCALE = 1.0f;
+    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
+            new AccelerateInterpolator();
+    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+            new DecelerateInterpolator();
+
+    public Animator createShowUpAnimator(final View target) {
+        if (mHasCustomAnimationParams) {
+            final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(
+                    target, View.SCALE_X, mShowUpStartXScale,
+                    KEY_PREVIEW_SHOW_UP_END_SCALE);
+            final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(
+                    target, View.SCALE_Y, mShowUpStartYScale,
+                    KEY_PREVIEW_SHOW_UP_END_SCALE);
+            final AnimatorSet showUpAnimator = new AnimatorSet();
+            showUpAnimator.play(scaleXAnimator).with(scaleYAnimator);
+            showUpAnimator.setDuration(mShowUpDuration);
+            showUpAnimator.setInterpolator(DECELERATE_INTERPOLATOR);
+            return showUpAnimator;
+        }
+        final Animator animator = AnimatorInflater.loadAnimator(
+                target.getContext(), mShowUpAnimatorResId);
+        animator.setTarget(target);
+        animator.setInterpolator(DECELERATE_INTERPOLATOR);
+        return animator;
     }
 
-    public int getShowUpDuration() {
-        return mShowUpDuration;
-    }
-
-    public float getDismissEndScale() {
-        return mDismissEndScale;
-    }
-
-    public int getDismissDuration() {
-        return mDismissDuration;
+    public Animator createDismissAnimator(final View target) {
+        if (mHasCustomAnimationParams) {
+            final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(
+                    target, View.SCALE_X, mDismissEndXScale);
+            final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(
+                    target, View.SCALE_Y, mDismissEndYScale);
+            final AnimatorSet dismissAnimator = new AnimatorSet();
+            dismissAnimator.play(scaleXAnimator).with(scaleYAnimator);
+            final int dismissDuration = Math.min(mDismissDuration, mLingerTimeout);
+            dismissAnimator.setDuration(dismissDuration);
+            dismissAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
+            return dismissAnimator;
+        }
+        final Animator animator = AnimatorInflater.loadAnimator(
+                target.getContext(), mDismissAnimatorResId);
+        animator.setTarget(target);
+        animator.setInterpolator(ACCELERATE_INTERPOLATOR);
+        return animator;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 63d848e..48f4c75 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -23,10 +23,16 @@
             "force_physical_keyboard_special_key";
     public static final String PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD =
             "pref_show_ui_to_accept_typed_word";
-    public static final String PREF_KEY_PREVIEW_SHOW_UP_START_SCALE =
-            "pref_key_preview_show_up_start_scale";
-    public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE =
-            "pref_key_preview_dismiss_end_scale";
+    public static final String PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS =
+            "pref_has_custom_key_preview_animation_params";
+    public static final String PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE =
+            "pref_key_preview_show_up_start_x_scale";
+    public static final String PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE =
+            "pref_key_preview_show_up_start_y_scale";
+    public static final String PREF_KEY_PREVIEW_DISMISS_END_X_SCALE =
+            "pref_key_preview_dismiss_end_x_scale";
+    public static final String PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE =
+            "pref_key_preview_dismiss_end_y_scale";
     public static final String PREF_KEY_PREVIEW_SHOW_UP_DURATION =
             "pref_key_preview_show_up_duration";
     public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
index dc2f88a..5640e20 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
@@ -78,12 +78,18 @@
                 res.getInteger(R.integer.config_key_preview_show_up_duration));
         setupKeyPreviewAnimationDuration(DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
                 res.getInteger(R.integer.config_key_preview_dismiss_duration));
-        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_SCALE,
-                ResourceUtils.getFloatFromFraction(
-                        res, R.fraction.config_key_preview_show_up_start_scale));
-        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_SCALE,
-                ResourceUtils.getFloatFromFraction(
-                        res, R.fraction.config_key_preview_dismiss_end_scale));
+        final float defaultKeyPreviewShowUpStartScale = ResourceUtils.getFloatFromFraction(
+                res, R.fraction.config_key_preview_show_up_start_scale);
+        final float defaultKeyPreviewDismissEndScale = ResourceUtils.getFloatFromFraction(
+                res, R.fraction.config_key_preview_dismiss_end_scale);
+        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE,
+                defaultKeyPreviewShowUpStartScale);
+        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE,
+                defaultKeyPreviewShowUpStartScale);
+        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE,
+                defaultKeyPreviewDismissEndScale);
+        setupKeyPreviewAnimationScale(DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE,
+                defaultKeyPreviewDismissEndScale);
 
         mServiceNeedsRestart = false;
         mDebugMode = (TwoStatePreference) findPreference(DebugSettings.PREF_DEBUG_MODE);
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index dc2eda9..1da112b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -103,10 +103,13 @@
 
     // Debug settings
     public final boolean mIsInternal;
+    public final boolean mHasCustomKeyPreviewAnimationParams;
     public final int mKeyPreviewShowUpDuration;
     public final int mKeyPreviewDismissDuration;
-    public final float mKeyPreviewShowUpStartScale;
-    public final float mKeyPreviewDismissEndScale;
+    public final float mKeyPreviewShowUpStartXScale;
+    public final float mKeyPreviewShowUpStartYScale;
+    public final float mKeyPreviewDismissEndXScale;
+    public final float mKeyPreviewDismissEndYScale;
 
     public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res,
             final InputAttributes inputAttributes) {
@@ -178,20 +181,30 @@
         mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
                 R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
         mIsInternal = Settings.isInternal(prefs);
+        mHasCustomKeyPreviewAnimationParams = prefs.getBoolean(
+                DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, false);
         mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
                 prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
                 res.getInteger(R.integer.config_key_preview_show_up_duration));
         mKeyPreviewDismissDuration = Settings.readKeyPreviewAnimationDuration(
                 prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
                 res.getInteger(R.integer.config_key_preview_dismiss_duration));
-        mKeyPreviewShowUpStartScale = Settings.readKeyPreviewAnimationScale(
-                prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_SCALE,
-                ResourceUtils.getFloatFromFraction(
-                        res, R.fraction.config_key_preview_show_up_start_scale));
-        mKeyPreviewDismissEndScale = Settings.readKeyPreviewAnimationScale(
-                prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_SCALE,
-                ResourceUtils.getFloatFromFraction(
-                        res, R.fraction.config_key_preview_dismiss_end_scale));
+        final float defaultKeyPreviewShowUpStartScale = ResourceUtils.getFloatFromFraction(
+                res, R.fraction.config_key_preview_show_up_start_scale);
+        final float defaultKeyPreviewDismissEndScale = ResourceUtils.getFloatFromFraction(
+                res, R.fraction.config_key_preview_dismiss_end_scale);
+        mKeyPreviewShowUpStartXScale = Settings.readKeyPreviewAnimationScale(
+                prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE,
+                defaultKeyPreviewShowUpStartScale);
+        mKeyPreviewShowUpStartYScale = Settings.readKeyPreviewAnimationScale(
+                prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE,
+                defaultKeyPreviewShowUpStartScale);
+        mKeyPreviewDismissEndXScale = Settings.readKeyPreviewAnimationScale(
+                prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE,
+                defaultKeyPreviewDismissEndScale);
+        mKeyPreviewDismissEndYScale = Settings.readKeyPreviewAnimationScale(
+                prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE,
+                defaultKeyPreviewDismissEndScale);
         mDisplayOrientation = res.getConfiguration().orientation;
         mAppWorkarounds = new AsyncResultHolder<>();
         final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
@@ -424,10 +437,14 @@
         sb.append("" + mKeyPreviewShowUpDuration);
         sb.append("\n   mKeyPreviewDismissDuration = ");
         sb.append("" + mKeyPreviewDismissDuration);
-        sb.append("\n   mKeyPreviewShowUpStartScale = ");
-        sb.append("" + mKeyPreviewShowUpStartScale);
-        sb.append("\n   mKeyPreviewDismissEndScale = ");
-        sb.append("" + mKeyPreviewDismissEndScale);
+        sb.append("\n   mKeyPreviewShowUpStartScaleX = ");
+        sb.append("" + mKeyPreviewShowUpStartXScale);
+        sb.append("\n   mKeyPreviewShowUpStartScaleY = ");
+        sb.append("" + mKeyPreviewShowUpStartYScale);
+        sb.append("\n   mKeyPreviewDismissEndScaleX = ");
+        sb.append("" + mKeyPreviewDismissEndXScale);
+        sb.append("\n   mKeyPreviewDismissEndScaleY = ");
+        sb.append("" + mKeyPreviewDismissEndYScale);
         return sb.toString();
     }
 }