[Scummvm-git-logs] scummvm branch-2-2 -> acc8aad2bac402518156932854d016194d04573a

antoniou79 a.antoniou79 at gmail.com
Thu Oct 15 15:39:37 UTC 2020


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
acc8aad2ba ANDROID: Use a in-app keyboard instead of system


Commit: acc8aad2bac402518156932854d016194d04573a
    https://github.com/scummvm/scummvm/commit/acc8aad2bac402518156932854d016194d04573a
Author: antoniou (a.antoniou79 at gmail.com)
Date: 2020-10-15T18:39:24+03:00

Commit Message:
ANDROID: Use a in-app keyboard instead of system

Keyboard was ported over from our SDL port which used https://github.com/pelya/commandergenius/tree/sdl_android/project

Pending optimizations, floatable/draggable implementation and a few bug fixes
We are using a local copy and slightly modified version of KeyboardView and Keyboard (and related resources).
since the android KeyboardView widget will be deprecated in API 29.
The copies are taken from the AOSP, as per the recommendation from Android Developers.

Changed paths:
  A backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
  A backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
  A dists/android/res/drawable-hdpi/btn_close_normal.png
  A dists/android/res/drawable-hdpi/btn_close_pressed.png
  A dists/android/res/drawable-hdpi/btn_close_selected.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_normal.9.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
  A dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
  A dists/android/res/drawable-hdpi/keyboard_background.9.png
  A dists/android/res/drawable-hdpi/keyboard_key_feedback_background.9.png
  A dists/android/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
  A dists/android/res/drawable-hdpi/keyboard_popup_panel_background.9.png
  A dists/android/res/drawable-ldpi/btn_close_normal.png
  A dists/android/res/drawable-ldpi/btn_close_pressed.png
  A dists/android/res/drawable-ldpi/btn_close_selected.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_normal.9.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_normal_off.9.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_normal_on.9.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_pressed.9.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_off.9.png
  A dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_on.9.png
  A dists/android/res/drawable-ldpi/keyboard_background.9.png
  A dists/android/res/drawable-ldpi/keyboard_key_feedback_background.9.png
  A dists/android/res/drawable-ldpi/keyboard_key_feedback_more_background.9.png
  A dists/android/res/drawable-ldpi/keyboard_popup_panel_background.9.png
  A dists/android/res/drawable-mdpi/btn_close_normal.png
  A dists/android/res/drawable-mdpi/btn_close_pressed.png
  A dists/android/res/drawable-mdpi/btn_close_selected.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_normal.9.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_normal_off.9.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_normal_on.9.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_pressed.9.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_off.9.png
  A dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_on.9.png
  A dists/android/res/drawable-mdpi/keyboard_background.9.png
  A dists/android/res/drawable-mdpi/keyboard_key_feedback_background.9.png
  A dists/android/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
  A dists/android/res/drawable-mdpi/keyboard_popup_panel_background.9.png
  A dists/android/res/drawable-xhdpi/btn_close_normal.png
  A dists/android/res/drawable-xhdpi/btn_close_pressed.png
  A dists/android/res/drawable-xhdpi/btn_close_selected.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_normal.9.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_off.9.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_on.9.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed.9.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_off.9.png
  A dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_on.9.png
  A dists/android/res/drawable-xhdpi/keyboard_background.9.png
  A dists/android/res/drawable-xhdpi/keyboard_key_feedback_background.9.png
  A dists/android/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png
  A dists/android/res/drawable-xhdpi/keyboard_popup_panel_background.9.png
  A dists/android/res/drawable/btn_close.xml
  A dists/android/res/drawable/btn_keyboard_key.xml
  A dists/android/res/drawable/keyboard_key_feedback.xml
  A dists/android/res/layout/keyboard_key_preview.xml
  A dists/android/res/layout/keyboard_popup_keyboard.xml
  A dists/android/res/values/attrs_min.xml
  A dists/android/res/values/config.xml
  A dists/android/res/values/styles.xml
  A dists/android/res/xml/qwerty.xml
  A dists/android/res/xml/qwerty_alt.xml
  A dists/android/res/xml/qwerty_alt_shift.xml
  A dists/android/res/xml/qwerty_shift.xml
  R dists/android/res/layout/main.xml
    backends/platform/android/org/scummvm/scummvm/EditableAccommodatingLatinIMETypeNullIssues.java
    backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
    backends/platform/android/org/scummvm/scummvm/MouseHelper.java
    backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
    backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
    backends/platform/android/org/scummvm/scummvm/SplashActivity.java
    dists/android/res/values/colors.xml
    dists/android/res/values/strings.xml
    dists/android/res/values/themes.xml


diff --git a/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java b/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
new file mode 100755
index 0000000000..7b246fec4b
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
@@ -0,0 +1,902 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ *
+ * 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 org.scummvm.scummvm;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import androidx.annotation.XmlRes;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/**
+ * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
+ * consists of rows of keys.
+ * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
+ * <pre>
+ * <Keyboard
+ *         android:keyWidth="%10p"
+ *         android:keyHeight="50px"
+ *         android:horizontalGap="2px"
+ *         android:verticalGap="2px" >
+ *     <Row android:keyWidth="32px" >
+ *         <Key android:keyLabel="A" />
+ *         ...
+ *     </Row>
+ *     ...
+ * </Keyboard>
+ * </pre>
+ */
+public class CustomKeyboard {
+
+    static final String LOG_TAG = "CustomKeyboard";
+
+    // Keyboard XML Tags
+    private static final String TAG_KEYBOARD = "CustomKeyboard";
+    private static final String TAG_ROW = "CustomRow";
+    private static final String TAG_KEY = "CustomKey";
+
+    public static final int EDGE_LEFT = 0x01;
+    public static final int EDGE_RIGHT = 0x02;
+    public static final int EDGE_TOP = 0x04;
+    public static final int EDGE_BOTTOM = 0x08;
+
+    public static final int KEYCODE_SHIFT = -1;
+    public static final int KEYCODE_MODE_CHANGE = -2;
+    public static final int KEYCODE_CANCEL = -3;
+    public static final int KEYCODE_DONE = -4;
+    public static final int KEYCODE_DELETE = -5;
+    public static final int KEYCODE_ALT = -6;
+
+    /** Keyboard label **/
+    private CharSequence mLabel;
+
+    /** Horizontal gap default for all rows */
+    private int mDefaultHorizontalGap;
+
+    /** Default key width */
+    private int mDefaultWidth;
+
+    /** Default key height */
+    private int mDefaultHeight;
+
+    /** Default gap between rows */
+    private int mDefaultVerticalGap;
+
+    /** Is the keyboard in the shifted state */
+    private boolean mShifted;
+
+    /** Key instance for the shift key, if present */
+    private CustomKey[] mShiftKeys = { null, null };
+
+    /** Key index for the shift key, if present */
+    private int[] mShiftKeyIndices = {-1, -1};
+
+    /** Current key width, while loading the keyboard */
+    private int mKeyWidth;
+
+    /** Current key height, while loading the keyboard */
+    private int mKeyHeight;
+
+    /** Total height of the keyboard, including the padding and keys */
+//    @UnsupportedAppUsage
+    private int mTotalHeight;
+
+    /**
+     * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
+     * right side.
+     */
+//    @UnsupportedAppUsage
+    private int mTotalWidth;
+
+    /** List of keys in this keyboard */
+    private List<CustomKey> mKeys;
+
+    /** List of modifier keys such as Shift & Alt, if any */
+//    @UnsupportedAppUsage
+    private List<CustomKey> mModifierKeys;
+
+    /** Width of the screen available to fit the keyboard */
+    private int mDisplayWidth;
+
+    /** Height of the screen */
+    private int mDisplayHeight;
+
+    /** Keyboard mode, or zero, if none.  */
+    private int mKeyboardMode;
+
+    // Variables for pre-computing nearest keys.
+
+    private static final int GRID_WIDTH = 10;
+    private static final int GRID_HEIGHT = 5;
+    private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
+    private int mCellWidth;
+    private int mCellHeight;
+    private int[][] mGridNeighbors;
+    private int mProximityThreshold;
+    /** Number of key widths from current touch point to search for nearest keys. */
+    private static float SEARCH_DISTANCE = 1.8f;
+
+    private ArrayList<CustomRow> rows = new ArrayList<CustomRow>();
+
+    /**
+     * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+     * Some of the key size defaults can be overridden per row from what the {@link CustomKeyboard}
+     * defines.
+     */
+    public static class CustomRow {
+        /** Default width of a key in this row. */
+        public int defaultWidth;
+        /** Default height of a key in this row. */
+        public int defaultHeight;
+        /** Default horizontal gap between keys in this row. */
+        public int defaultHorizontalGap;
+        /** Vertical gap following this row. */
+        public int verticalGap;
+
+        ArrayList<CustomKey> mKeys = new ArrayList<>();
+
+        /**
+         * Edge flags for this row of keys. Possible values that can be assigned are
+         * {@link CustomKeyboard#EDGE_TOP EDGE_TOP} and {@link CustomKeyboard#EDGE_BOTTOM EDGE_BOTTOM}
+         */
+        public int rowEdgeFlags;
+
+        /** The keyboard mode for this row */
+        public int mode;
+
+        private CustomKeyboard parent;
+
+        public CustomRow(CustomKeyboard parent) {
+            this.parent = parent;
+        }
+
+        public CustomRow(Resources res, CustomKeyboard parent, XmlResourceParser parser) {
+            this.parent = parent;
+            TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.CustomKeyboard);
+            defaultWidth = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_keyWidth,
+                    parent.mDisplayWidth, parent.mDefaultWidth);
+            defaultHeight = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_keyHeight,
+                    parent.mDisplayHeight, parent.mDefaultHeight);
+            defaultHorizontalGap = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_horizontalGap,
+                    parent.mDisplayWidth, parent.mDefaultHorizontalGap);
+            verticalGap = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_verticalGap,
+                    parent.mDisplayHeight, parent.mDefaultVerticalGap);
+            a.recycle();
+
+            a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.CustomKeyboard_CustomRow);
+            rowEdgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomRow_rowEdgeFlags, 0);
+            mode = a.getResourceId(R.styleable.CustomKeyboard_CustomRow_keyboardMode,
+                    0);
+			a.recycle();
+        }
+    }
+
+    /**
+     * Class for describing the position and characteristics of a single key in the keyboard.
+     *
+     */
+    public static class CustomKey {
+        /**
+         * All the key codes (unicode or custom code) that this key could generate, zero'th
+         * being the most important.
+         */
+        public int[] codes;
+
+        /** Label to display */
+        public CharSequence label;
+
+        /** Icon to display instead of a label. Icon takes precedence over a label */
+        public Drawable icon;
+        /** Preview version of the icon, for the preview popup */
+        public Drawable iconPreview;
+        /** Width of the key, not including the gap */
+        public int width;
+        /** Height of the key, not including the gap */
+        public int height;
+        /** The horizontal gap before this key */
+        public int gap;
+        /** Whether this key is sticky, i.e., a toggle key */
+        public boolean sticky;
+        /** X coordinate of the key in the keyboard layout */
+        public int x;
+        /** Y coordinate of the key in the keyboard layout */
+        public int y;
+        /** The current pressed state of this key */
+        public boolean pressed;
+        /** If this is a sticky key, is it on? */
+        public boolean on;
+        /** Text to output when pressed. This can be multiple characters, like ".com" */
+        public CharSequence text;
+        /** Popup characters */
+        public CharSequence popupCharacters;
+
+        /**
+         * Flags that specify the anchoring to edges of the keyboard for detecting touch events
+         * that are just out of the boundary of the key. This is a bit mask of
+         * {@link CustomKeyboard#EDGE_LEFT}, {@link CustomKeyboard#EDGE_RIGHT}, {@link CustomKeyboard#EDGE_TOP} and
+         * {@link CustomKeyboard#EDGE_BOTTOM}.
+         */
+        public int edgeFlags;
+        /** Whether this is a modifier key, such as Shift or Alt */
+        public boolean modifier;
+        /** The keyboard that this key belongs to */
+        private CustomKeyboard keyboard;
+        /**
+         * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
+         * keyboard.
+         */
+        public int popupResId;
+        /** Whether this key repeats itself when held down */
+        public boolean repeatable;
+
+
+        private final static int[] KEY_STATE_NORMAL_ON = {
+            android.R.attr.state_checkable,
+            android.R.attr.state_checked
+        };
+
+        private final static int[] KEY_STATE_PRESSED_ON = {
+            android.R.attr.state_pressed,
+            android.R.attr.state_checkable,
+            android.R.attr.state_checked
+        };
+
+        private final static int[] KEY_STATE_NORMAL_OFF = {
+            android.R.attr.state_checkable
+        };
+
+        private final static int[] KEY_STATE_PRESSED_OFF = {
+            android.R.attr.state_pressed,
+            android.R.attr.state_checkable
+        };
+
+        private final static int[] KEY_STATE_NORMAL = {
+        };
+
+        private final static int[] KEY_STATE_PRESSED = {
+            android.R.attr.state_pressed
+        };
+
+        /** Create an empty key with no attributes. */
+        public CustomKey(CustomRow parent) {
+            keyboard = parent.parent;
+            height = parent.defaultHeight;
+            width = parent.defaultWidth;
+            gap = parent.defaultHorizontalGap;
+            edgeFlags = parent.rowEdgeFlags;
+        }
+
+        /** Create a key with the given top-left coordinate and extract its attributes from
+         * the XML parser.
+         * @param res resources associated with the caller's context
+         * @param parent the row that this key belongs to. The row must already be attached to
+         * a {@link CustomKeyboard}.
+         * @param x the x coordinate of the top-left
+         * @param y the y coordinate of the top-left
+         * @param parser the XML parser containing the attributes for this key
+         */
+        public CustomKey(Resources res, CustomRow parent, int x, int y, XmlResourceParser parser) {
+            this(parent);
+
+            this.x = x;
+            this.y = y;
+
+            // obtainAttributes (AttributeSet set, int[] attrs)
+			// Retrieve a set of basic attribute values from an AttributeSet, not performing styling of them using a theme and/or style resources.
+            TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.CustomKeyboard);
+
+            width = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_keyWidth,
+                    keyboard.mDisplayWidth, parent.defaultWidth);
+            height = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_keyHeight,
+                    keyboard.mDisplayHeight, parent.defaultHeight);
+            gap = getDimensionOrFraction(a,
+                    R.styleable.CustomKeyboard_horizontalGap,
+                    keyboard.mDisplayWidth, parent.defaultHorizontalGap);
+
+//            Log.d(LOG_TAG, "from CustomKeyboard: wid: " +width + " heigh: " + height + " gap: " + gap );
+
+            a.recycle();
+            a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.CustomKeyboard_CustomKey);
+            this.x += gap;
+            TypedValue codesValue = new TypedValue();
+            a.getValue(R.styleable.CustomKeyboard_CustomKey_codes,
+                    codesValue);
+            if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) {
+//				Log.d(LOG_TAG, "Key codes is INT or HEX");
+                codes = new int[] { codesValue.data };
+            } else if (codesValue.type == TypedValue.TYPE_STRING) {
+//				Log.d(LOG_TAG, "Key codes is String");
+                codes = parseCSV(codesValue.string.toString());
+            }
+
+            iconPreview = a.getDrawable(R.styleable.CustomKeyboard_CustomKey_iconPreview);
+            if (iconPreview != null) {
+                iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
+                        iconPreview.getIntrinsicHeight());
+            }
+            popupCharacters = a.getText(
+                    R.styleable.CustomKeyboard_CustomKey_popupCharacters);
+            popupResId = a.getResourceId(
+                    R.styleable.CustomKeyboard_CustomKey_popupKeyboard, 0);
+            repeatable = a.getBoolean(
+                    R.styleable.CustomKeyboard_CustomKey_isRepeatable, false);
+            modifier = a.getBoolean(
+                    R.styleable.CustomKeyboard_CustomKey_isModifier, false);
+            sticky = a.getBoolean(
+                    R.styleable.CustomKeyboard_CustomKey_isSticky, false);
+            edgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomKey_keyEdgeFlags, 0);
+            edgeFlags |= parent.rowEdgeFlags;
+
+            icon = a.getDrawable(
+                    R.styleable.CustomKeyboard_CustomKey_keyIcon);
+            if (icon != null) {
+                icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+            }
+            label = a.getText(R.styleable.CustomKeyboard_CustomKey_keyLabel);
+			Log.d(LOG_TAG, "Key label is " + label);
+
+			text = a.getText(R.styleable.CustomKeyboard_CustomKey_keyOutputText);
+
+            if (codes == null && !TextUtils.isEmpty(label)) {
+                codes = new int[] { label.charAt(0) };
+            }
+            a.recycle();
+        }
+
+        /**
+         * Informs the key that it has been pressed, in case it needs to change its appearance or
+         * state.
+         * @see #onReleased(boolean)
+         */
+        public void onPressed() {
+            pressed = !pressed;
+        }
+
+        /**
+         * Changes the pressed state of the key.
+         *
+         * <p>Toggled state of the key will be flipped when all the following conditions are
+         * fulfilled:</p>
+         *
+         * <ul>
+         *     <li>This is a sticky key, that is, {@link #sticky} is {@code true}.
+         *     <li>The parameter {@code inside} is {@code true}.
+         *     <li>{@link android.os.Build.VERSION#SDK_INT} is greater than
+         *         {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+         * </ul>
+         *
+         * @param inside whether the finger was released inside the key. Works only on Android M and
+         * later. See the method document for details.
+         * @see #onPressed()
+         */
+        public void onReleased(boolean inside) {
+            pressed = !pressed;
+            if (sticky && inside) {
+                on = !on;
+            }
+        }
+
+        int[] parseCSV(String value) {
+            int count = 0;
+            int lastIndex = 0;
+            if (value.length() > 0) {
+                count++;
+                while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+                    count++;
+                }
+            }
+            int[] values = new int[count];
+            count = 0;
+            StringTokenizer st = new StringTokenizer(value, ",");
+            while (st.hasMoreTokens()) {
+                try {
+                    values[count++] = Integer.parseInt(st.nextToken());
+                } catch (NumberFormatException nfe) {
+                    Log.e(LOG_TAG, "Error parsing keycodes " + value);
+                }
+            }
+            return values;
+        }
+
+        /**
+         * Detects if a point falls inside this key.
+         * @param x the x-coordinate of the point
+         * @param y the y-coordinate of the point
+         * @return whether or not the point falls inside the key. If the key is attached to an edge,
+         * it will assume that all points between the key and the edge are considered to be inside
+         * the key.
+         */
+        public boolean isInside(int x, int y) {
+            boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
+            boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
+            boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
+            boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
+            if ((x >= this.x || (leftEdge && x <= this.x + this.width))
+                    && (x < this.x + this.width || (rightEdge && x >= this.x))
+                    && (y >= this.y || (topEdge && y <= this.y + this.height))
+                    && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /**
+         * Returns the square of the distance between the center of the key and the given point.
+         * @param x the x-coordinate of the point
+         * @param y the y-coordinate of the point
+         * @return the square of the distance of the point from the center of the key
+         */
+        public int squaredDistanceFrom(int x, int y) {
+            int xDist = this.x + width / 2 - x;
+            int yDist = this.y + height / 2 - y;
+            return xDist * xDist + yDist * yDist;
+        }
+
+        /**
+         * Returns the drawable state for the key, based on the current state and type of the key.
+         * @return the drawable state of the key.
+         * @see android.graphics.drawable.StateListDrawable#setState(int[])
+         */
+        public int[] getCurrentDrawableState() {
+            int[] states = KEY_STATE_NORMAL;
+
+            if (on) {
+                if (pressed) {
+                    states = KEY_STATE_PRESSED_ON;
+                } else {
+                    states = KEY_STATE_NORMAL_ON;
+                }
+            } else {
+                if (sticky) {
+                    if (pressed) {
+                        states = KEY_STATE_PRESSED_OFF;
+                    } else {
+                        states = KEY_STATE_NORMAL_OFF;
+                    }
+                } else {
+                    if (pressed) {
+                        states = KEY_STATE_PRESSED;
+                    }
+                }
+            }
+            return states;
+        }
+    }
+
+    /**
+     * Creates a keyboard from the given xml key layout file.
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     */
+    public CustomKeyboard(Context context, int xmlLayoutResId) {
+        this(context, xmlLayoutResId, 0);
+    }
+
+    /**
+     * Creates a keyboard from the given xml key layout file. Weeds out rows
+     * that have a keyboard mode defined but don't match the specified mode.
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     * @param modeId keyboard mode identifier
+     * @param width sets width of keyboard
+     * @param height sets height of keyboard
+     */
+    public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
+						  int height) {
+        mDisplayWidth = width;
+        mDisplayHeight = height;
+
+        mDefaultHorizontalGap = 0;
+        mDefaultWidth = mDisplayWidth / 10;
+        mDefaultVerticalGap = 0;
+        mDefaultHeight = mDefaultWidth;
+        mKeys = new ArrayList<CustomKey>();
+        mModifierKeys = new ArrayList<CustomKey>();
+        mKeyboardMode = modeId;
+        loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+    }
+
+    /**
+     * Creates a keyboard from the given xml key layout file. Weeds out rows
+     * that have a keyboard mode defined but don't match the specified mode.
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     * @param modeId keyboard mode identifier
+     */
+    public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
+        DisplayMetrics dm = context.getResources().getDisplayMetrics();
+        mDisplayWidth = dm.widthPixels;
+        mDisplayHeight = dm.heightPixels;
+        Log.v(LOG_TAG, "keyboard's display metrics:" + dm);
+
+        mDefaultHorizontalGap = 0;
+        mDefaultWidth = mDisplayWidth / 10;
+        mDefaultVerticalGap = 0;
+        mDefaultHeight = mDefaultWidth;
+        mKeys = new ArrayList<CustomKey>();
+        mModifierKeys = new ArrayList<CustomKey>();
+        mKeyboardMode = modeId;
+//		Log.v(LOG_TAG, "Resource ID is "  + xmlLayoutResId + " and parser is null?" + ((context.getResources().getXml(xmlLayoutResId) == null) ? "yes" : "no"));
+        loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+    }
+
+    /**
+     * <p>Creates a blank keyboard from the given resource file and populates it with the specified
+     * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
+     * </p>
+     * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
+     * possible in each row.</p>
+     * @param context the application or service context
+     * @param layoutTemplateResId the layout template file, containing no keys.
+     * @param characters the list of characters to display on the keyboard. One key will be created
+     * for each character.
+     * @param columns the number of columns of keys to display. If this number is greater than the
+     * number of keys that can fit in a row, it will be ignored. If this number is -1, the
+     * keyboard will fit as many keys as possible in each row.
+     */
+    public CustomKeyboard(Context context, int layoutTemplateResId,
+						  CharSequence characters, int columns, int horizontalPadding) {
+        this(context, layoutTemplateResId);
+        int x = 0;
+        int y = 0;
+        int column = 0;
+        mTotalWidth = 0;
+
+        CustomRow row = new CustomRow(this);
+        row.defaultHeight = mDefaultHeight;
+        row.defaultWidth = mDefaultWidth;
+        row.defaultHorizontalGap = mDefaultHorizontalGap;
+        row.verticalGap = mDefaultVerticalGap;
+        row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
+        final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
+        for (int i = 0; i < characters.length(); i++) {
+            char c = characters.charAt(i);
+            if (column >= maxColumns
+                    || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
+                x = 0;
+                y += mDefaultVerticalGap + mDefaultHeight;
+                column = 0;
+            }
+            final CustomKey key = new CustomKey(row);
+            key.x = x;
+            key.y = y;
+            key.label = String.valueOf(c);
+            key.codes = new int[] { c };
+            column++;
+            x += key.width + key.gap;
+            mKeys.add(key);
+            row.mKeys.add(key);
+            if (x > mTotalWidth) {
+                mTotalWidth = x;
+            }
+        }
+        mTotalHeight = y + mDefaultHeight;
+        rows.add(row);
+    }
+
+//    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    final void resize(int newWidth, int newHeight) {
+        int numRows = rows.size();
+        for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
+            CustomRow row = rows.get(rowIndex);
+            int numKeys = row.mKeys.size();
+            int totalGap = 0;
+            int totalWidth = 0;
+            for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
+				CustomKey key = row.mKeys.get(keyIndex);
+                if (keyIndex > 0) {
+                    totalGap += key.gap;
+                }
+                totalWidth += key.width;
+            }
+            if (totalGap + totalWidth > newWidth) {
+                int x = 0;
+                float scaleFactor = (float)(newWidth - totalGap) / totalWidth;
+                for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
+					CustomKey key = row.mKeys.get(keyIndex);
+                    key.width *= scaleFactor;
+                    key.x = x;
+                    x += key.width + key.gap;
+                }
+            }
+        }
+        mTotalWidth = newWidth;
+        // TODO: This does not adjust the vertical placement according to the new size.
+        // The main problem in the previous code was horizontal placement/size, but we should
+        // also recalculate the vertical sizes/positions when we get this resize call.
+    }
+
+    public List<CustomKey> getKeys() {
+        return mKeys;
+    }
+
+    public List<CustomKey> getModifierKeys() {
+        return mModifierKeys;
+    }
+
+    protected int getHorizontalGap() {
+        return mDefaultHorizontalGap;
+    }
+
+    protected void setHorizontalGap(int gap) {
+        mDefaultHorizontalGap = gap;
+    }
+
+    protected int getVerticalGap() {
+        return mDefaultVerticalGap;
+    }
+
+    protected void setVerticalGap(int gap) {
+        mDefaultVerticalGap = gap;
+    }
+
+    protected int getKeyHeight() {
+        return mDefaultHeight;
+    }
+
+    protected void setKeyHeight(int height) {
+        mDefaultHeight = height;
+    }
+
+    protected int getKeyWidth() {
+        return mDefaultWidth;
+    }
+
+    protected void setKeyWidth(int width) {
+        mDefaultWidth = width;
+    }
+
+    /**
+     * Returns the total height of the keyboard
+     * @return the total height of the keyboard
+     */
+    public int getHeight() {
+        return mTotalHeight;
+    }
+
+    public int getMinWidth() {
+        return mTotalWidth;
+    }
+
+    public boolean setShifted(boolean shiftState) {
+        for (CustomKey shiftKey : mShiftKeys) {
+            if (shiftKey != null) {
+                shiftKey.on = shiftState;
+            }
+        }
+        if (mShifted != shiftState) {
+            mShifted = shiftState;
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isShifted() {
+        return mShifted;
+    }
+
+    /**
+     */
+    public int[] getShiftKeyIndices() {
+        return mShiftKeyIndices;
+    }
+
+    public int getShiftKeyIndex() {
+        return mShiftKeyIndices[0];
+    }
+
+    private void computeNearestNeighbors() {
+        // Round-up so we don't have any pixels outside the grid
+        mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
+        mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
+        mGridNeighbors = new int[GRID_SIZE][];
+        int[] indices = new int[mKeys.size()];
+        final int gridWidth = GRID_WIDTH * mCellWidth;
+        final int gridHeight = GRID_HEIGHT * mCellHeight;
+        for (int x = 0; x < gridWidth; x += mCellWidth) {
+            for (int y = 0; y < gridHeight; y += mCellHeight) {
+                int count = 0;
+                for (int i = 0; i < mKeys.size(); i++) {
+                    final CustomKey key = mKeys.get(i);
+                    if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
+                            key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
+                            key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
+                                < mProximityThreshold ||
+                            key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
+                        indices[count++] = i;
+                    }
+                }
+                int [] cell = new int[count];
+                System.arraycopy(indices, 0, cell, 0, count);
+                mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
+            }
+        }
+    }
+
+    /**
+     * Returns the indices of the keys that are closest to the given point.
+     * @param x the x-coordinate of the point
+     * @param y the y-coordinate of the point
+     * @return the array of integer indices for the nearest keys to the given point. If the given
+     * point is out of range, then an array of size zero is returned.
+     */
+    public int[] getNearestKeys(int x, int y) {
+        if (mGridNeighbors == null) computeNearestNeighbors();
+        if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
+            int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
+            if (index < GRID_SIZE) {
+                return mGridNeighbors[index];
+            }
+        }
+        return new int[0];
+    }
+
+    protected CustomRow createRowFromXml(Resources res, XmlResourceParser parser) {
+        return new CustomRow(res, this, parser);
+    }
+
+    protected CustomKey createKeyFromXml(Resources res, CustomRow parent, int x, int y,
+										 XmlResourceParser parser) {
+        return new CustomKey(res, parent, x, y, parser);
+    }
+
+    private void loadKeyboard(Context context, XmlResourceParser parser) {
+        boolean inKey = false;
+        boolean inRow = false;
+        boolean leftMostKey = false;
+        int row = 0;
+        int x = 0;
+        int y = 0;
+		CustomKey key = null;
+        CustomRow currentRow = null;
+        Resources res = context.getResources();
+        boolean skipRow = false;
+
+        try {
+            int event;
+            while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+                if (event == XmlResourceParser.START_TAG) {
+                    String tag = parser.getName();
+                    if (TAG_ROW.equals(tag)) {
+						//Log.d(LOG_TAG, "TAG ROW");
+                        inRow = true;
+                        x = 0;
+                        currentRow = createRowFromXml(res, parser);
+                        rows.add(currentRow);
+                        skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
+                        if (skipRow) {
+                            skipToEndOfRow(parser);
+                            inRow = false;
+                        }
+                   } else if (TAG_KEY.equals(tag)) {
+						//Log.d(LOG_TAG, "TAG KEY");
+                        inKey = true;
+                        key = createKeyFromXml(res, currentRow, x, y, parser);
+                        mKeys.add(key);
+						if (key.codes != null) {
+							if (key.codes[0] == KEYCODE_SHIFT) {
+								// Find available shift key slot and put this shift key in it
+								for (int i = 0; i < mShiftKeys.length; i++) {
+									if (mShiftKeys[i] == null) {
+										mShiftKeys[i] = key;
+										mShiftKeyIndices[i] = mKeys.size()-1;
+										break;
+									}
+								}
+								mModifierKeys.add(key);
+							} else if (key.codes[0] == KEYCODE_ALT) {
+								mModifierKeys.add(key);
+							}
+							currentRow.mKeys.add(key);
+						}
+                    } else if (TAG_KEYBOARD.equals(tag)) {
+                        parseKeyboardAttributes(res, parser);
+                    }
+                } else if (event == XmlResourceParser.END_TAG) {
+                    if (inKey) {
+                        inKey = false;
+                        x += key.gap + key.width;
+                        if (x > mTotalWidth) {
+                            mTotalWidth = x;
+                        }
+                    } else if (inRow) {
+                        inRow = false;
+                        y += currentRow.verticalGap;
+                        y += currentRow.defaultHeight;
+                        row++;
+                    } else {
+                        // TODO: error or extend?
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Parse error:" + e);
+            e.printStackTrace();
+        }
+        mTotalHeight = y - mDefaultVerticalGap;
+    }
+
+    private void skipToEndOfRow(XmlResourceParser parser)
+            throws XmlPullParserException, IOException {
+        int event;
+        while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+            if (event == XmlResourceParser.END_TAG
+                    && parser.getName().equals(TAG_ROW)) {
+                break;
+            }
+        }
+    }
+
+    private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
+        TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.CustomKeyboard);
+
+        mDefaultWidth = getDimensionOrFraction(a,
+                R.styleable.CustomKeyboard_keyWidth,
+                mDisplayWidth, mDisplayWidth / 10);
+        mDefaultHeight = getDimensionOrFraction(a,
+                R.styleable.CustomKeyboard_keyHeight,
+                mDisplayHeight, 50);
+        mDefaultHorizontalGap = getDimensionOrFraction(a,
+                R.styleable.CustomKeyboard_horizontalGap,
+                mDisplayWidth, 0);
+        mDefaultVerticalGap = getDimensionOrFraction(a,
+                R.styleable.CustomKeyboard_verticalGap,
+                mDisplayHeight, 0);
+        mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
+        mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
+        a.recycle();
+    }
+
+    static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
+        TypedValue value = a.peekValue(index);
+        if (value == null) return defValue;
+        if (value.type == TypedValue.TYPE_DIMENSION) {
+            return a.getDimensionPixelOffset(index, defValue);
+        } else if (value.type == TypedValue.TYPE_FRACTION) {
+            // Round it to avoid values like 47.9999 from getting truncated
+            return Math.round(a.getFraction(index, base, base, defValue));
+        }
+        return defValue;
+    }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java b/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
new file mode 100755
index 0000000000..6b1c95d337
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
@@ -0,0 +1,1652 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ *
+ * 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.
+ *
+ * Using Copy from the AOSP project as suggested by:
+ * https://developer.android.com/reference/android/inputmethodservice/KeyboardView
+ */
+
+package org.scummvm.scummvm;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view that renders a virtual {@link CustomKeyboard}. It handles rendering of keys and
+ * detecting key presses and touch movements.
+ */
+
+public class CustomKeyboardView extends View implements View.OnClickListener {
+
+    /**
+     * Listener for virtual keyboard events.
+     */
+    public interface OnKeyboardActionListener {
+
+        /**
+         * Called when the user presses a key. This is sent before the {@link #onKey} is called.
+         * For keys that repeat, this is only called once.
+         * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
+         * key, the value will be zero.
+         */
+        void onPress(int primaryCode);
+
+        /**
+         * Called when the user releases a key. This is sent after the {@link #onKey} is called.
+         * For keys that repeat, this is only called once.
+         * @param primaryCode the code of the key that was released
+         */
+        void onRelease(int primaryCode);
+
+        /**
+         * Send a key press to the listener.
+         * @param primaryCode this is the key that was pressed
+         * @param keyCodes the codes for all the possible alternative keys
+         * with the primary code being the first. If the primary key code is
+         * a single character such as an alphabet or number or symbol, the alternatives
+         * will include other characters that may be on the same key or adjacent keys.
+         * These codes are useful to correct for accidental presses of a key adjacent to
+         * the intended key.
+         */
+        void onKey(int primaryCode, int[] keyCodes);
+
+        /**
+         * Sends a sequence of characters to the listener.
+         * @param text the sequence of characters to be displayed.
+         */
+        void onText(CharSequence text);
+
+        /**
+         * Called when the user quickly moves the finger from right to left.
+         */
+        void swipeLeft();
+
+        /**
+         * Called when the user quickly moves the finger from left to right.
+         */
+        void swipeRight();
+
+        /**
+         * Called when the user quickly moves the finger from up to down.
+         */
+        void swipeDown();
+
+        /**
+         * Called when the user quickly moves the finger from down to up.
+         */
+        void swipeUp();
+    }
+
+    private Context mContext;
+
+    private static final boolean DEBUG = false;
+    private static final int NOT_A_KEY = -1;
+    private static final int[] KEY_DELETE = { CustomKeyboard.KEYCODE_DELETE };
+    private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
+
+    private CustomKeyboard mKeyboard;
+    private int mCurrentKeyIndex = NOT_A_KEY;
+
+//    @UnsupportedAppUsage
+    private int mLabelTextSize;
+
+    private int mKeyTextSize;
+    private int mKeyTextColor;
+    private float mShadowRadius;
+    private int mShadowColor;
+    private float mBackgroundDimAmount;
+
+//    @UnsupportedAppUsage
+    private TextView mPreviewText;
+
+    private PopupWindow mPreviewPopup;
+    private int mPreviewTextSizeLarge;
+    private int mPreviewOffset;
+    private int mPreviewHeight;
+    // Working variable
+    private final int[] mCoordinates = new int[2];
+
+    private PopupWindow mPopupKeyboard;
+    private View mMiniKeyboardContainer;
+    private CustomKeyboardView mMiniKeyboard;
+    private boolean mMiniKeyboardOnScreen;
+    private View mPopupParent;
+    private int mMiniKeyboardOffsetX;
+    private int mMiniKeyboardOffsetY;
+    private Map<CustomKeyboard.CustomKey,View> mMiniKeyboardCache;
+    private CustomKeyboard.CustomKey[] mKeys;
+
+    /** Listener for {@link OnKeyboardActionListener}. */
+    private OnKeyboardActionListener mKeyboardActionListener;
+
+    private static final int MSG_SHOW_PREVIEW = 1;
+    private static final int MSG_REMOVE_PREVIEW = 2;
+    private static final int MSG_REPEAT = 3;
+    private static final int MSG_LONGPRESS = 4;
+
+    private static final int DELAY_BEFORE_PREVIEW = 0;
+    private static final int DELAY_AFTER_PREVIEW = 70;
+    private static final int DEBOUNCE_TIME = 70;
+
+    private int mVerticalCorrection;
+    private int mProximityThreshold;
+
+    private boolean mPreviewCentered = false;
+    private boolean mShowPreview = true;
+    private boolean mShowTouchPoints = true;
+    private int mPopupPreviewX;
+    private int mPopupPreviewY;
+
+    private int mLastX;
+    private int mLastY;
+    private int mStartX;
+    private int mStartY;
+
+    private boolean mProximityCorrectOn;
+
+    private Paint mPaint;
+    private Rect mPadding;
+
+    private long mDownTime;
+    private long mLastMoveTime;
+    private int mLastKey;
+    private int mLastCodeX;
+    private int mLastCodeY;
+    private int mCurrentKey = NOT_A_KEY;
+    private int mDownKey = NOT_A_KEY;
+    private long mLastKeyTime;
+    private long mCurrentKeyTime;
+    private int[] mKeyIndices = new int[12];
+    private GestureDetector mGestureDetector;
+    private int mPopupX;
+    private int mPopupY;
+    private int mRepeatKeyIndex = NOT_A_KEY;
+    private int mPopupLayout;
+    private boolean mAbortKey;
+    private CustomKeyboard.CustomKey mInvalidatedKey;
+    private Rect mClipRegion = new Rect(0, 0, 0, 0);
+    private boolean mPossiblePoly;
+    private SwipeTracker mSwipeTracker = new SwipeTracker();
+    private int mSwipeThreshold;
+    private boolean mDisambiguateSwipe;
+
+    // Variables for dealing with multiple pointers
+    private int mOldPointerCount = 1;
+    private float mOldPointerX;
+    private float mOldPointerY;
+
+	// @UnsupportedAppUsage
+	// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/UnsupportedAppUsage.java
+	// Indicates that a class member, that is not part of the SDK, is used by apps.
+	// Since the member is not part of the SDK, such use is not supported.
+	//
+	// This annotation acts as a heads up that changing a given method or field
+	// may affect apps, potentially breaking them when the next Android version is
+	// released. In some cases, for members that are heavily used, this annotation
+	// may imply restrictions on changes to the member.
+    private Drawable mKeyBackground;
+
+    private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+    private static final int REPEAT_START_DELAY = 400;
+    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+
+    private static int MAX_NEARBY_KEYS = 12;
+    private int[] mDistances = new int[MAX_NEARBY_KEYS];
+
+    // For multi-tap
+    private int mLastSentIndex;
+    private int mTapCount;
+    private long mLastTapTime;
+    private boolean mInMultiTap;
+    private static final int MULTITAP_INTERVAL = 800; // milliseconds
+    private StringBuilder mPreviewLabel = new StringBuilder(1);
+
+    /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
+    private boolean mDrawPending;
+    /** The dirty region in the keyboard bitmap */
+    private Rect mDirtyRect = new Rect();
+    /** The keyboard bitmap for faster updates */
+    private Bitmap mBuffer;
+    /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
+    private boolean mKeyboardChanged;
+    /** The canvas for the above mutable keyboard bitmap */
+    private Canvas mCanvas;
+    /** The accessibility manager for accessibility support */
+    private AccessibilityManager mAccessibilityManager;
+    /** The audio manager for accessibility support */
+    private AudioManager mAudioManager;
+    /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
+    private boolean mHeadsetRequiredToHearPasswordsAnnounced;
+
+	// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occur”) based on:
+	// https://stackoverflow.com/a/27826094
+	public static class CustomKeyboardViewHandler extends Handler {
+
+		private final WeakReference<CustomKeyboardView> mListenerReference;
+
+		public CustomKeyboardViewHandler(CustomKeyboardView listener) {
+			mListenerReference = new WeakReference<>(listener);
+		}
+
+		@Override
+		public synchronized void handleMessage(@NonNull Message msg) {
+			CustomKeyboardView listener = mListenerReference.get();
+			if(listener != null) {
+				switch (msg.what) {
+					case MSG_SHOW_PREVIEW:
+						listener.showKey(msg.arg1);
+						break;
+					case MSG_REMOVE_PREVIEW:
+						listener.mPreviewText.setVisibility(INVISIBLE);
+						break;
+					case MSG_REPEAT:
+						if (listener.repeatKey()) {
+							Message repeat = Message.obtain(this, MSG_REPEAT);
+							sendMessageDelayed(repeat, REPEAT_INTERVAL);
+						}
+						break;
+					case MSG_LONGPRESS:
+						listener.openPopupIfRequired((MotionEvent) msg.obj);
+						break;
+				}
+			}
+		}
+
+		public void clear() {
+			this.removeCallbacksAndMessages(null);
+		}
+	}
+
+//	Handler mHandler;
+//	final private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
+	private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
+
+	public void clearEventHandler() {
+		mHandler.clear();
+	}
+
+    public CustomKeyboardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+	public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+//		super(context, attrs, defStyleAttr, defStyleRes); // this call requires API 21. Skip it for now
+		super(context, attrs, defStyleAttr);
+
+		mContext = context;
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.CustomKeyboardView, defStyleAttr, defStyleRes);
+
+        LayoutInflater inflate =
+                (LayoutInflater) context
+                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        int previewLayout = 0;
+        int keyTextSize = 0;
+
+        int n = a.getIndexCount();
+
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+
+            switch (attr) {
+            case R.styleable.CustomKeyboardView_keyBackground:
+                mKeyBackground = a.getDrawable(attr);
+                break;
+            case R.styleable.CustomKeyboardView_verticalCorrection:
+                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case R.styleable.CustomKeyboardView_keyPreviewLayout:
+                previewLayout = a.getResourceId(attr, 0);
+                break;
+            case R.styleable.CustomKeyboardView_keyPreviewOffset:
+                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case R.styleable.CustomKeyboardView_keyPreviewHeight:
+                mPreviewHeight = a.getDimensionPixelSize(attr, 80);
+                break;
+            case R.styleable.CustomKeyboardView_keyTextSize:
+                mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+                break;
+            case R.styleable.CustomKeyboardView_keyTextColor:
+                mKeyTextColor = a.getColor(attr, 0xFF000000);
+                break;
+            case R.styleable.CustomKeyboardView_labelTextSize:
+                mLabelTextSize = a.getDimensionPixelSize(attr, 14);
+                break;
+            case R.styleable.CustomKeyboardView_popupLayout:
+                mPopupLayout = a.getResourceId(attr, 0);
+                break;
+            case R.styleable.CustomKeyboardView_shadowColor:
+                mShadowColor = a.getColor(attr, 0);
+                break;
+            case R.styleable.CustomKeyboardView_shadowRadius:
+                mShadowRadius = a.getFloat(attr, 0f);
+                break;
+            }
+        }
+
+//        // TODO put default values as constants somewhere
+//        if (mLabelTextSize == 0) {
+//			mLabelTextSize = 14;
+//		}
+//
+//		if (mKeyTextSize == 0) {
+//			mKeyTextSize = 18;
+//		}
+//
+//		if (mKeyTextColor == 0) {
+//			mKeyTextColor = 0xFF000000;
+//		}
+//
+//		if (mPreviewHeight == 0) {
+//			mPreviewHeight = 80;
+//		}
+
+		mBackgroundDimAmount = a.getFloat(R.styleable.CustomKeyboardView_backgroundDimAmount, 0.5f);
+		a.recycle();
+
+        mPreviewPopup = new PopupWindow(context);
+        if (previewLayout != 0) {
+            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
+            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
+            mPreviewPopup.setContentView(mPreviewText);
+            mPreviewPopup.setBackgroundDrawable(null);
+        } else {
+            mShowPreview = false;
+        }
+
+        mPreviewPopup.setTouchable(false);
+
+        mPopupKeyboard = new PopupWindow(context);
+        mPopupKeyboard.setBackgroundDrawable(null);
+        //mPopupKeyboard.setClippingEnabled(false);
+
+        mPopupParent = this;
+        //mPredicting = true;
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(keyTextSize);
+        mPaint.setTextAlign(Align.CENTER);
+        mPaint.setAlpha(255);
+
+        mPadding = new Rect(0, 0, 0, 0);
+        mMiniKeyboardCache = new HashMap<>();
+
+        if (mKeyBackground == null) {
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+				mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key, context.getTheme());
+			} else {
+				mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key);
+			}
+		}
+
+		mKeyBackground.getPadding(mPadding);
+
+        mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+        mDisambiguateSwipe = getResources().getBoolean(
+                R.bool.config_swipeDisambiguation);
+
+        //mAccessibilityManager = AccessibilityManager.getInstance(context);
+		mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        resetMultiTap();
+    }
+
+	@Override
+	protected void onAttachedToWindow() {
+		super.onAttachedToWindow();
+		initGestureDetector();
+		if (mHandler == null) {
+			mHandler = new CustomKeyboardViewHandler(this);
+		}
+	}
+
+    private void initGestureDetector() {
+        if (mGestureDetector == null) {
+            mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+                @Override
+                public boolean onFling(MotionEvent me1, MotionEvent me2,
+                        float velocityX, float velocityY) {
+                    if (mPossiblePoly) return false;
+                    final float absX = Math.abs(velocityX);
+                    final float absY = Math.abs(velocityY);
+                    float deltaX = me2.getX() - me1.getX();
+                    float deltaY = me2.getY() - me1.getY();
+                    int travelX = getWidth() / 2; // Half the keyboard width
+                    int travelY = getHeight() / 2; // Half the keyboard height
+                    mSwipeTracker.computeCurrentVelocity(1000);
+                    final float endingVelocityX = mSwipeTracker.getXVelocity();
+                    final float endingVelocityY = mSwipeTracker.getYVelocity();
+                    boolean sendDownKey = false;
+                    if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
+                        if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeRight();
+                            return true;
+                        }
+                    } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
+                        if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeLeft();
+                            return true;
+                        }
+                    } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
+                        if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeUp();
+                            return true;
+                        }
+                    } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                        if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
+                            sendDownKey = true;
+                        } else {
+                            swipeDown();
+                            return true;
+                        }
+                    }
+
+                    if (sendDownKey) {
+                        detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
+                    }
+                    return false;
+                }
+            });
+
+            mGestureDetector.setIsLongpressEnabled(false);
+        }
+    }
+
+    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+    }
+
+    /**
+     * Returns the {@link OnKeyboardActionListener} object.
+     * @return the listener attached to this keyboard
+     */
+    protected OnKeyboardActionListener getOnKeyboardActionListener() {
+        return mKeyboardActionListener;
+    }
+
+    /**
+     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+     * view will re-layout itself to accommodate the keyboard.
+     * @see CustomKeyboard
+     * @see #getKeyboard()
+     * @param keyboard the keyboard to display in this view
+     */
+    public void setKeyboard(CustomKeyboard keyboard) {
+        if (mKeyboard != null) {
+            showPreview(NOT_A_KEY);
+        }
+        // Remove any pending messages
+        removeMessages();
+        mKeyboard = keyboard;
+        List<CustomKeyboard.CustomKey> keys = mKeyboard.getKeys();
+//        mKeys = keys.toArray(new CustomKeyboard.Key[keys.size()]);
+		mKeys = keys.toArray(new CustomKeyboard.CustomKey[0]);
+        requestLayout();
+        // Hint to reallocate the buffer if the size changed
+        mKeyboardChanged = true;
+        invalidateAllKeys();
+        computeProximityThreshold(keyboard);
+        mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
+        // Switching to a different keyboard should abort any pending keys so that the key up
+        // doesn't get delivered to the old or new keyboard
+        mAbortKey = true; // Until the next ACTION_DOWN
+    }
+
+    /**
+     * Returns the current keyboard being displayed by this view.
+     * @return the currently attached keyboard
+     * @see #setKeyboard(CustomKeyboard)
+     */
+    public CustomKeyboard getKeyboard() {
+        return mKeyboard;
+    }
+
+    /**
+     * Sets the state of the shift key of the keyboard, if any.
+     * @param shifted whether or not to enable the state of the shift key
+     * @return true if the shift key state changed, false if there was no change
+     * @see CustomKeyboardView#isShifted()
+     */
+    public boolean setShifted(boolean shifted) {
+        if (mKeyboard != null) {
+            if (mKeyboard.setShifted(shifted)) {
+                // The whole keyboard probably needs to be redrawn
+                invalidateAllKeys();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the state of the shift key of the keyboard, if any.
+     * @return true if the shift is in a pressed state, false otherwise. If there is
+     * no shift key on the keyboard or there is no keyboard attached, it returns false.
+     * @see CustomKeyboardView#setShifted(boolean)
+     */
+    public boolean isShifted() {
+        if (mKeyboard != null) {
+            return mKeyboard.isShifted();
+        }
+        return false;
+    }
+
+    /**
+     * Enables or disables the key feedback 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 popup
+     * @see #isPreviewEnabled()
+     */
+    public void setPreviewEnabled(boolean previewEnabled) {
+        mShowPreview = previewEnabled;
+    }
+
+    /**
+     * Returns the enabled state of the key feedback popup.
+     * @return whether or not the key feedback popup is enabled
+     * @see #setPreviewEnabled(boolean)
+     */
+    public boolean isPreviewEnabled() {
+        return mShowPreview;
+    }
+
+    public void setVerticalCorrection(int verticalOffset) {
+
+    }
+    public void setPopupParent(View v) {
+        mPopupParent = v;
+    }
+
+    public void setPopupOffset(int x, int y) {
+        mMiniKeyboardOffsetX = x;
+        mMiniKeyboardOffsetY = y;
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+    }
+
+    /**
+     * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
+     * codes for adjacent keys.  When disabled, only the primary key code will be
+     * reported.
+     * @param enabled whether or not the proximity correction is enabled
+     */
+    public void setProximityCorrectionEnabled(boolean enabled) {
+        mProximityCorrectOn = enabled;
+    }
+
+    /**
+     * Returns true if proximity correction is enabled.
+     */
+    public boolean isProximityCorrectionEnabled() {
+        return mProximityCorrectOn;
+    }
+
+    /**
+     * Popup keyboard close button clicked.
+     */
+    public void onClick(View v) {
+        dismissPopupKeyboard();
+    }
+
+    private CharSequence adjustCase(CharSequence label) {
+        if (mKeyboard.isShifted() && label != null && label.length() < 3
+                && Character.isLowerCase(label.charAt(0))) {
+            label = label.toString().toUpperCase();
+        }
+        return label;
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Round up a little
+        if (mKeyboard == null) {
+            setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
+        } else {
+            int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
+            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
+                width = MeasureSpec.getSize(widthMeasureSpec);
+            }
+            setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
+        }
+    }
+
+    /**
+     * Compute the average distance between adjacent keys (horizontally and vertically)
+     * and square it to get the proximity threshold. We use a square here and in computing
+     * the touch distance from a key's center to avoid taking a square root.
+     * @param keyboard
+     */
+    private void computeProximityThreshold(CustomKeyboard keyboard) {
+        if (keyboard == null) return;
+        final CustomKeyboard.CustomKey[] keys = mKeys;
+        if (keys == null) return;
+        int length = keys.length;
+        int dimensionSum = 0;
+        for (int i = 0; i < length; i++) {
+			CustomKeyboard.CustomKey key = keys[i];
+            dimensionSum += Math.min(key.width, key.height) + key.gap;
+        }
+        if (dimensionSum < 0 || length == 0) return;
+        mProximityThreshold = (int) (dimensionSum * 1.4f / length);
+        mProximityThreshold *= mProximityThreshold; // Square it
+    }
+
+    @Override
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        if (mKeyboard != null) {
+            mKeyboard.resize(w, h);
+        }
+        // Release the buffer, if any and it will be reallocated on the next draw
+        mBuffer = null;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mDrawPending || mBuffer == null || mKeyboardChanged) {
+            onBufferDraw();
+        }
+        canvas.drawBitmap(mBuffer, 0, 0, null);
+    }
+
+    private void onBufferDraw() {
+        if (mBuffer == null || mKeyboardChanged) {
+            if (mBuffer == null || mKeyboardChanged &&
+                    (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
+                // Make sure our bitmap is at least 1x1
+                final int width = Math.max(1, getWidth());
+                final int height = Math.max(1, getHeight());
+                mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                mCanvas = new Canvas(mBuffer);
+            }
+            invalidateAllKeys();
+            mKeyboardChanged = false;
+        }
+
+        if (mKeyboard == null) return;
+
+        mCanvas.save();
+        final Canvas canvas = mCanvas;
+        canvas.clipRect(mDirtyRect);
+
+        final Paint paint = mPaint;
+        final Drawable keyBackground = mKeyBackground;
+        final Rect clipRegion = mClipRegion;
+        final Rect padding = mPadding;
+        final int kbdPaddingLeft = getPaddingLeft();
+        final int kbdPaddingTop = getPaddingTop();
+        final CustomKeyboard.CustomKey[] keys = mKeys;
+        final CustomKeyboard.CustomKey invalidKey = mInvalidatedKey;
+
+        paint.setColor(mKeyTextColor);
+        boolean drawSingleKey = false;
+        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
+          // Is clipRegion completely contained within the invalidated key?
+          if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
+                  invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
+                  invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
+                  invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
+              drawSingleKey = true;
+          }
+        }
+        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+        final int keyCount = keys.length;
+        for (int i = 0; i < keyCount; i++) {
+            final CustomKeyboard.CustomKey key = keys[i];
+            if (drawSingleKey && invalidKey != key) {
+                continue;
+            }
+            int[] drawableState = key.getCurrentDrawableState();
+            keyBackground.setState(drawableState);
+
+//			Log.d("keyboardView", " key label: " + (key.label == null ? "null" : key.label.toString()));
+            // Switch the character to uppercase if shift is pressed
+            String label = key.label == null? null : adjustCase(key.label).toString();
+
+            final Rect bounds = keyBackground.getBounds();
+            if (key.width != bounds.right ||
+                    key.height != bounds.bottom) {
+                keyBackground.setBounds(0, 0, key.width, key.height);
+            }
+            canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+            keyBackground.draw(canvas);
+
+            if (label != null) {
+                // For characters, use large font. For labels like "Done", use small font.
+                if (label.length() > 1 && key.codes.length < 2) {
+                    paint.setTextSize(mLabelTextSize);
+                    paint.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {
+                    paint.setTextSize(mKeyTextSize);
+                    paint.setTypeface(Typeface.DEFAULT);
+                }
+                // Draw a drop shadow for the text
+                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+                // Draw the text
+				Log.d("keyboardView", "keyW: " + key.width +
+					" keyH: " + key.height +
+					" padL: " + padding.left +
+					" padR: " + padding.right +
+					" padT: " + padding.top +
+					" padB: " + padding.bottom +
+					" paintTs: " + paint.getTextSize() +
+					" paintDesce: " + paint.descent());
+
+				Log.d("keyboardView", " Draw key: " + label
+					+ " x: " + ( ((key.width  - padding.left - padding.right)  / 2.0f ) + padding.left)
+					+ " y: " + ( ((key.height - padding.top  - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top));
+                canvas.drawText(label,
+					( ((key.width  - padding.left - padding.right)  / 2.0f ) + padding.left),
+					( ((key.height - padding.top  - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top),
+                    paint);
+                // Turn off drop shadow
+                paint.setShadowLayer(0, 0, 0, 0);
+            } else if (key.icon != null) {
+                final int drawableX = (key.width - padding.left - padding.right
+                                - key.icon.getIntrinsicWidth()) / 2 + padding.left;
+                final int drawableY = (key.height - padding.top - padding.bottom
+                        - key.icon.getIntrinsicHeight()) / 2 + padding.top;
+                canvas.translate(drawableX, drawableY);
+                key.icon.setBounds(0, 0,
+                        key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
+                key.icon.draw(canvas);
+                canvas.translate(-drawableX, -drawableY);
+            }
+            canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+        }
+        mInvalidatedKey = null;
+        // Overlay a dark rectangle to dim the keyboard
+        if (mMiniKeyboardOnScreen) {
+            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+        }
+
+        if (DEBUG && mShowTouchPoints) {
+            paint.setAlpha(128);
+            paint.setColor(0xFFFF0000);
+            canvas.drawCircle(mStartX, mStartY, 3, paint);
+            canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
+            paint.setColor(0xFF0000FF);
+            canvas.drawCircle(mLastX, mLastY, 3, paint);
+            paint.setColor(0xFF00FF00);
+            canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
+        }
+        mCanvas.restore();
+        mDrawPending = false;
+        mDirtyRect.setEmpty();
+    }
+
+    private int getKeyIndices(int x, int y, int[] allKeys) {
+        final CustomKeyboard.CustomKey[] keys = mKeys;
+        int primaryIndex = NOT_A_KEY;
+        int closestKey = NOT_A_KEY;
+        int closestKeyDist = mProximityThreshold + 1;
+        java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
+        int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+        final int keyCount = nearestKeyIndices.length;
+        for (int i = 0; i < keyCount; i++) {
+            final CustomKeyboard.CustomKey key = keys[nearestKeyIndices[i]];
+            int dist = 0;
+            boolean isInside = key.isInside(x,y);
+            if (isInside) {
+                primaryIndex = nearestKeyIndices[i];
+            }
+
+            if (((mProximityCorrectOn
+                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
+                    || isInside)
+                    && key.codes[0] > 32) {
+                // Find insertion point
+                final int nCodes = key.codes.length;
+                if (dist < closestKeyDist) {
+                    closestKeyDist = dist;
+                    closestKey = nearestKeyIndices[i];
+                }
+
+                if (allKeys == null) continue;
+
+                for (int j = 0; j < mDistances.length; j++) {
+                    if (mDistances[j] > dist) {
+                        // Make space for nCodes codes
+                        System.arraycopy(mDistances, j, mDistances, j + nCodes,
+                                mDistances.length - j - nCodes);
+                        System.arraycopy(allKeys, j, allKeys, j + nCodes,
+                                allKeys.length - j - nCodes);
+                        for (int c = 0; c < nCodes; c++) {
+                            allKeys[j + c] = key.codes[c];
+                            mDistances[j + c] = dist;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        if (primaryIndex == NOT_A_KEY) {
+            primaryIndex = closestKey;
+        }
+        return primaryIndex;
+    }
+
+    private void detectAndSendKey(int index, int x, int y, long eventTime) {
+        if (index != NOT_A_KEY && index < mKeys.length) {
+            final CustomKeyboard.CustomKey key = mKeys[index];
+            if (key.text != null) {
+                mKeyboardActionListener.onText(key.text);
+                mKeyboardActionListener.onRelease(NOT_A_KEY);
+            } else {
+                int code = key.codes[0];
+                //TextEntryState.keyPressedAt(key, x, y);
+                int[] codes = new int[MAX_NEARBY_KEYS];
+                Arrays.fill(codes, NOT_A_KEY);
+                getKeyIndices(x, y, codes);
+                // Multi-tap
+                if (mInMultiTap) {
+                    if (mTapCount != -1) {
+                        mKeyboardActionListener.onKey(CustomKeyboard.KEYCODE_DELETE, KEY_DELETE);
+                    } else {
+                        mTapCount = 0;
+                    }
+                    code = key.codes[mTapCount];
+                }
+                mKeyboardActionListener.onKey(code, codes);
+                mKeyboardActionListener.onRelease(code);
+            }
+            mLastSentIndex = index;
+            mLastTapTime = eventTime;
+        }
+    }
+
+    /**
+     * Handle multi-tap keys by producing the key label for the current multi-tap state.
+     */
+    private CharSequence getPreviewText(CustomKeyboard.CustomKey key) {
+        if (mInMultiTap) {
+            // Multi-tap
+            mPreviewLabel.setLength(0);
+            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+            return adjustCase(mPreviewLabel);
+        } else {
+            return adjustCase(key.label);
+        }
+    }
+
+    private void showPreview(int keyIndex) {
+        int oldKeyIndex = mCurrentKeyIndex;
+        final PopupWindow previewPopup = mPreviewPopup;
+
+        mCurrentKeyIndex = keyIndex;
+        // Release the old key and press the new key
+        final CustomKeyboard.CustomKey[] keys = mKeys;
+        if (oldKeyIndex != mCurrentKeyIndex) {
+            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
+				CustomKeyboard.CustomKey oldKey = keys[oldKeyIndex];
+                oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
+                invalidateKey(oldKeyIndex);
+                final int keyCode = oldKey.codes[0];
+                sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
+                        keyCode);
+                // TODO: We need to implement AccessibilityNodeProvider for this view.
+                sendAccessibilityEventForUnicodeCharacter(
+                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
+            }
+            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
+				CustomKeyboard.CustomKey newKey = keys[mCurrentKeyIndex];
+                newKey.onPressed();
+                invalidateKey(mCurrentKeyIndex);
+                final int keyCode = newKey.codes[0];
+                sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                        keyCode);
+                // TODO: We need to implement AccessibilityNodeProvider for this view.
+                sendAccessibilityEventForUnicodeCharacter(
+                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
+            }
+        }
+        // If key changed and preview is on ...
+        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+            mHandler.removeMessages(MSG_SHOW_PREVIEW);
+            if (previewPopup.isShowing()) {
+                if (keyIndex == NOT_A_KEY) {
+                    mHandler.sendMessageDelayed(mHandler
+                            .obtainMessage(MSG_REMOVE_PREVIEW),
+                            DELAY_AFTER_PREVIEW);
+                }
+            }
+            if (keyIndex != NOT_A_KEY) {
+                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+                    // Show right away, if it's already visible and finger is moving around
+                    showKey(keyIndex);
+                } else {
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
+                            DELAY_BEFORE_PREVIEW);
+                }
+            }
+        }
+    }
+
+//    @UnsupportedAppUsage
+    private void showKey(final int keyIndex) {
+        final PopupWindow previewPopup = mPreviewPopup;
+        final CustomKeyboard.CustomKey[] keys = mKeys;
+        if (keyIndex < 0 || keyIndex >= mKeys.length) return;
+		CustomKeyboard.CustomKey key = keys[keyIndex];
+        if (key.icon != null) {
+            mPreviewText.setCompoundDrawables(null, null, null,
+                    key.iconPreview != null ? key.iconPreview : key.icon);
+            mPreviewText.setText(null);
+        } else {
+            mPreviewText.setCompoundDrawables(null, null, null, null);
+            mPreviewText.setText(getPreviewText(key));
+            if (key.label.length() > 1 && key.codes.length < 2) {
+                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
+                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+            } else {
+                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
+                mPreviewText.setTypeface(Typeface.DEFAULT);
+            }
+        }
+        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
+                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+        final int popupHeight = mPreviewHeight;
+        LayoutParams lp = mPreviewText.getLayoutParams();
+        if (lp != null) {
+            lp.width = popupWidth;
+            lp.height = popupHeight;
+        }
+        if (!mPreviewCentered) {
+            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
+            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+        } else {
+            // TODO: Fix this if centering is brought back
+            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+            mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+        }
+        mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+        getLocationInWindow(mCoordinates);
+        mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
+        mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
+
+        // Set the preview background state
+        mPreviewText.getBackground().setState(
+                key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+        mPopupPreviewX += mCoordinates[0];
+        mPopupPreviewY += mCoordinates[1];
+
+        // If the popup cannot be shown above the key, put it on the side
+        getLocationOnScreen(mCoordinates);
+        if (mPopupPreviewY + mCoordinates[1] < 0) {
+            // If the key you're pressing is on the left side of the keyboard, show the popup on
+            // the right, offset by enough to see at least one key to the left/right.
+            if (key.x + key.width <= getWidth() / 2) {
+                mPopupPreviewX += (int) (key.width * 2.5);
+            } else {
+                mPopupPreviewX -= (int) (key.width * 2.5);
+            }
+            mPopupPreviewY += popupHeight;
+        }
+
+        if (previewPopup.isShowing()) {
+            previewPopup.update(mPopupPreviewX, mPopupPreviewY,
+                    popupWidth, popupHeight);
+        } else {
+            previewPopup.setWidth(popupWidth);
+            previewPopup.setHeight(popupHeight);
+            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+                    mPopupPreviewX, mPopupPreviewY);
+        }
+        mPreviewText.setVisibility(VISIBLE);
+    }
+
+    private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
+        if (mAccessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+            onInitializeAccessibilityEvent(event);
+            final String text;
+            switch (code) {
+                case CustomKeyboard.KEYCODE_ALT:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_alt);
+                    break;
+                case CustomKeyboard.KEYCODE_CANCEL:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_cancel);
+                    break;
+                case CustomKeyboard.KEYCODE_DELETE:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_delete);
+                    break;
+                case CustomKeyboard.KEYCODE_DONE:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_done);
+                    break;
+                case CustomKeyboard.KEYCODE_MODE_CHANGE:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_mode_change);
+                    break;
+                case CustomKeyboard.KEYCODE_SHIFT:
+                    text = mContext.getString(R.string.customkeyboardview_keycode_shift);
+                    break;
+                case '\n':
+                    text = mContext.getString(R.string.customkeyboardview_keycode_enter);
+                    break;
+                default:
+                    text = String.valueOf((char) code);
+            }
+            event.getText().add(text);
+            mAccessibilityManager.sendAccessibilityEvent(event);
+        }
+    }
+
+    /**
+     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
+     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
+     * draws the cached buffer.
+     * @see #invalidateKey(int)
+     */
+    public void invalidateAllKeys() {
+        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mDrawPending = true;
+        invalidate();
+    }
+
+    /**
+     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
+     * one key is changing it's content. Any changes that affect the position or size of the key
+     * may not be honored.
+     * @param keyIndex the index of the key in the attached {@link CustomKeyboard}.
+     * @see #invalidateAllKeys
+     */
+    public void invalidateKey(int keyIndex) {
+        if (mKeys == null) return;
+        if (keyIndex < 0 || keyIndex >= mKeys.length) {
+            return;
+        }
+        final CustomKeyboard.CustomKey key = mKeys[keyIndex];
+        mInvalidatedKey = key;
+        mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
+                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+        onBufferDraw();
+        invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),
+                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+    }
+
+//    @UnsupportedAppUsage
+    private boolean openPopupIfRequired(MotionEvent me) {
+        // Check if we have a popup layout specified first.
+        if (mPopupLayout == 0) {
+            return false;
+        }
+        if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
+            return false;
+        }
+
+		CustomKeyboard.CustomKey popupKey = mKeys[mCurrentKey];
+        boolean result = onLongPress(popupKey);
+        if (result) {
+            mAbortKey = true;
+            showPreview(NOT_A_KEY);
+        }
+        return result;
+    }
+
+    /**
+     * Called when a key is long pressed. By default this will open any popup keyboard associated
+     * with this key through the attributes popupLayout and popupCharacters.
+     * @param popupKey the key that was long pressed
+     * @return true if the long press is handled, false otherwise. Subclasses should call the
+     * method on the base class if the subclass doesn't wish to handle the call.
+     */
+    protected boolean onLongPress(CustomKeyboard.CustomKey popupKey) {
+        int popupKeyboardId = popupKey.popupResId;
+
+        if (popupKeyboardId != 0) {
+            mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
+            if (mMiniKeyboardContainer == null) {
+                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+                mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
+                mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(
+					android.R.id.keyboardView);
+                View closeButton = mMiniKeyboardContainer.findViewById(
+					android.R.id.closeButton);
+                if (closeButton != null) closeButton.setOnClickListener(this);
+                mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+                    public void onKey(int primaryCode, int[] keyCodes) {
+                        mKeyboardActionListener.onKey(primaryCode, keyCodes);
+                        dismissPopupKeyboard();
+                    }
+
+                    public void onText(CharSequence text) {
+                        mKeyboardActionListener.onText(text);
+                        dismissPopupKeyboard();
+                    }
+
+                    public void swipeLeft() { }
+                    public void swipeRight() { }
+                    public void swipeUp() { }
+                    public void swipeDown() { }
+                    public void onPress(int primaryCode) {
+                        mKeyboardActionListener.onPress(primaryCode);
+                    }
+                    public void onRelease(int primaryCode) {
+                        mKeyboardActionListener.onRelease(primaryCode);
+                    }
+                });
+                //mInputView.setSuggest(mSuggest);
+				CustomKeyboard keyboard;
+                if (popupKey.popupCharacters != null) {
+                    keyboard = new CustomKeyboard(getContext(), popupKeyboardId,
+                            popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
+                } else {
+                    keyboard = new CustomKeyboard(getContext(), popupKeyboardId);
+                }
+                mMiniKeyboard.setKeyboard(keyboard);
+                mMiniKeyboard.setPopupParent(this);
+                mMiniKeyboardContainer.measure(
+                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+
+                mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
+            } else {
+                mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(
+					android.R.id.keyboardView);
+            }
+            getLocationInWindow(mCoordinates);
+            mPopupX = popupKey.x + getPaddingLeft();
+            mPopupY = popupKey.y + getPaddingTop();
+            mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
+            mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
+            final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
+            final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
+            mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+            mMiniKeyboard.setShifted(isShifted());
+            mPopupKeyboard.setContentView(mMiniKeyboardContainer);
+            mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+            mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+            mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+            mMiniKeyboardOnScreen = true;
+            //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+            invalidateAllKeys();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
+            final int action = event.getAction();
+            switch (action) {
+                case MotionEvent.ACTION_HOVER_ENTER: {
+                    event.setAction(MotionEvent.ACTION_DOWN);
+                } break;
+                case MotionEvent.ACTION_HOVER_MOVE: {
+                    event.setAction(MotionEvent.ACTION_MOVE);
+                } break;
+                case MotionEvent.ACTION_HOVER_EXIT: {
+                    event.setAction(MotionEvent.ACTION_UP);
+                } break;
+            }
+            return onTouchEvent(event);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        // Convert multi-pointer up/down events to single up/down events to
+        // deal with the typical multi-pointer behavior of two-thumb typing
+        final int pointerCount = me.getPointerCount();
+        final int action = me.getAction();
+        boolean result = false;
+        final long now = me.getEventTime();
+
+        if (pointerCount != mOldPointerCount) {
+            if (pointerCount == 1) {
+                // Send a down event for the latest pointer
+                MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
+                        me.getX(), me.getY(), me.getMetaState());
+                result = onModifiedTouchEvent(down, false);
+                down.recycle();
+                // If it's an up action, then deliver the up as well.
+                if (action == MotionEvent.ACTION_UP) {
+                	//// TODO should we do this performClick here?
+					//performClick();
+                    result = onModifiedTouchEvent(me, true);
+                }
+            } else {
+                // Send an up event for the last pointer
+                MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
+                        mOldPointerX, mOldPointerY, me.getMetaState());
+                result = onModifiedTouchEvent(up, true);
+				//// TODO should we do this performClick here?
+				//performClick();
+                up.recycle();
+            }
+        } else {
+            if (pointerCount == 1) {
+				// TODO should we do this performClick here?
+				//if (action == MotionEvent.ACTION_UP) {
+				//	performClick();
+				//}
+                result = onModifiedTouchEvent(me, false);
+                mOldPointerX = me.getX();
+                mOldPointerY = me.getY();
+            } else {
+                // Don't do anything when 2 pointers are down and moving.
+                result = true;
+            }
+        }
+        mOldPointerCount = pointerCount;
+
+        return result;
+    }
+
+	@Override
+	public boolean performClick() {
+		super.performClick();
+		return true;
+	}
+
+    private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
+        int touchX = (int) me.getX() - getPaddingLeft();
+        int touchY = (int) me.getY() - getPaddingTop();
+        if (touchY >= -mVerticalCorrection)
+            touchY += mVerticalCorrection;
+        final int action = me.getAction();
+        final long eventTime = me.getEventTime();
+        int keyIndex = getKeyIndices(touchX, touchY, null);
+        mPossiblePoly = possiblePoly;
+
+        // Track the last few movements to look for spurious swipes.
+        if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
+        mSwipeTracker.addMovement(me);
+
+        // Ignore all motion events until a DOWN.
+        if (mAbortKey
+                && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
+            return true;
+        }
+
+        if (mGestureDetector.onTouchEvent(me)) {
+            showPreview(NOT_A_KEY);
+            mHandler.removeMessages(MSG_REPEAT);
+            mHandler.removeMessages(MSG_LONGPRESS);
+            return true;
+        }
+
+        // Needs to be called after the gesture detector gets a turn, as it may have
+        // displayed the mini keyboard
+        if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
+            return true;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mAbortKey = false;
+                mStartX = touchX;
+                mStartY = touchY;
+                mLastCodeX = touchX;
+                mLastCodeY = touchY;
+                mLastKeyTime = 0;
+                mCurrentKeyTime = 0;
+                mLastKey = NOT_A_KEY;
+                mCurrentKey = keyIndex;
+                mDownKey = keyIndex;
+                mDownTime = me.getEventTime();
+                mLastMoveTime = mDownTime;
+                checkMultiTap(eventTime, keyIndex);
+                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
+                        mKeys[keyIndex].codes[0] : 0);
+                if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
+                    mRepeatKeyIndex = mCurrentKey;
+                    Message msg = mHandler.obtainMessage(MSG_REPEAT);
+                    mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+                    repeatKey();
+                    // Delivering the key could have caused an abort
+                    if (mAbortKey) {
+                        mRepeatKeyIndex = NOT_A_KEY;
+                        break;
+                    }
+                }
+                if (mCurrentKey != NOT_A_KEY) {
+                    Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+                    mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+                }
+                showPreview(keyIndex);
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                boolean continueLongPress = false;
+                if (keyIndex != NOT_A_KEY) {
+                    if (mCurrentKey == NOT_A_KEY) {
+                        mCurrentKey = keyIndex;
+                        mCurrentKeyTime = eventTime - mDownTime;
+                    } else {
+                        if (keyIndex == mCurrentKey) {
+                            mCurrentKeyTime += eventTime - mLastMoveTime;
+                            continueLongPress = true;
+                        } else if (mRepeatKeyIndex == NOT_A_KEY) {
+                            resetMultiTap();
+                            mLastKey = mCurrentKey;
+                            mLastCodeX = mLastX;
+                            mLastCodeY = mLastY;
+                            mLastKeyTime =
+                                    mCurrentKeyTime + eventTime - mLastMoveTime;
+                            mCurrentKey = keyIndex;
+                            mCurrentKeyTime = 0;
+                        }
+                    }
+                }
+                if (!continueLongPress) {
+                    // Cancel old longpress
+                    mHandler.removeMessages(MSG_LONGPRESS);
+                    // Start new longpress if key has changed
+                    if (keyIndex != NOT_A_KEY) {
+                        Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+                        mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+                    }
+                }
+                showPreview(mCurrentKey);
+                mLastMoveTime = eventTime;
+                break;
+
+            case MotionEvent.ACTION_UP:
+                removeMessages();
+                if (keyIndex == mCurrentKey) {
+                    mCurrentKeyTime += eventTime - mLastMoveTime;
+                } else {
+                    resetMultiTap();
+                    mLastKey = mCurrentKey;
+                    mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+                    mCurrentKey = keyIndex;
+                    mCurrentKeyTime = 0;
+                }
+                if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
+                        && mLastKey != NOT_A_KEY) {
+                    mCurrentKey = mLastKey;
+                    touchX = mLastCodeX;
+                    touchY = mLastCodeY;
+                }
+                showPreview(NOT_A_KEY);
+                Arrays.fill(mKeyIndices, NOT_A_KEY);
+                // If we're not on a repeating key (which sends on a DOWN event)
+                if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
+                    detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
+                }
+                invalidateKey(keyIndex);
+                mRepeatKeyIndex = NOT_A_KEY;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                removeMessages();
+                dismissPopupKeyboard();
+                mAbortKey = true;
+                showPreview(NOT_A_KEY);
+                invalidateKey(mCurrentKey);
+                break;
+        }
+        mLastX = touchX;
+        mLastY = touchY;
+        return true;
+    }
+
+//    @UnsupportedAppUsage
+    private boolean repeatKey() {
+		CustomKeyboard.CustomKey key = mKeys[mRepeatKeyIndex];
+        detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
+        return true;
+    }
+
+    protected void swipeRight() {
+        mKeyboardActionListener.swipeRight();
+    }
+
+    protected void swipeLeft() {
+        mKeyboardActionListener.swipeLeft();
+    }
+
+    protected void swipeUp() {
+        mKeyboardActionListener.swipeUp();
+    }
+
+    protected void swipeDown() {
+        mKeyboardActionListener.swipeDown();
+    }
+
+    public void closing() {
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+        removeMessages();
+
+        dismissPopupKeyboard();
+        mBuffer = null;
+        mCanvas = null;
+        mMiniKeyboardCache.clear();
+    }
+
+    private void removeMessages() {
+        if (mHandler != null) {
+            mHandler.removeMessages(MSG_REPEAT);
+            mHandler.removeMessages(MSG_LONGPRESS);
+            mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        closing();
+    }
+
+    private void dismissPopupKeyboard() {
+        if (mPopupKeyboard.isShowing()) {
+            mPopupKeyboard.dismiss();
+            mMiniKeyboardOnScreen = false;
+            invalidateAllKeys();
+        }
+    }
+
+    public boolean handleBack() {
+        if (mPopupKeyboard.isShowing()) {
+            dismissPopupKeyboard();
+            return true;
+        }
+        return false;
+    }
+
+    private void resetMultiTap() {
+        mLastSentIndex = NOT_A_KEY;
+        mTapCount = 0;
+        mLastTapTime = -1;
+        mInMultiTap = false;
+    }
+
+    private void checkMultiTap(long eventTime, int keyIndex) {
+        if (keyIndex == NOT_A_KEY) return;
+		CustomKeyboard.CustomKey key = mKeys[keyIndex];
+        if (key.codes.length > 1) {
+            mInMultiTap = true;
+            if (eventTime < mLastTapTime + MULTITAP_INTERVAL
+                    && keyIndex == mLastSentIndex) {
+                mTapCount = (mTapCount + 1) % key.codes.length;
+                return;
+            } else {
+                mTapCount = -1;
+                return;
+            }
+        }
+        if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+            resetMultiTap();
+        }
+    }
+
+    private static class SwipeTracker {
+
+        static final int NUM_PAST = 4;
+        static final int LONGEST_PAST_TIME = 200;
+
+        final float mPastX[] = new float[NUM_PAST];
+        final float mPastY[] = new float[NUM_PAST];
+        final long mPastTime[] = new long[NUM_PAST];
+
+        float mYVelocity;
+        float mXVelocity;
+
+        public void clear() {
+            mPastTime[0] = 0;
+        }
+
+        public void addMovement(MotionEvent ev) {
+            long time = ev.getEventTime();
+            final int N = ev.getHistorySize();
+            for (int i=0; i<N; i++) {
+                addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
+                        ev.getHistoricalEventTime(i));
+            }
+            addPoint(ev.getX(), ev.getY(), time);
+        }
+
+        private void addPoint(float x, float y, long time) {
+            int drop = -1;
+            int i;
+            final long[] pastTime = mPastTime;
+            for (i=0; i<NUM_PAST; i++) {
+                if (pastTime[i] == 0) {
+                    break;
+                } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
+                    drop = i;
+                }
+            }
+            if (i == NUM_PAST && drop < 0) {
+                drop = 0;
+            }
+            if (drop == i) drop--;
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            if (drop >= 0) {
+                final int start = drop+1;
+                final int count = NUM_PAST-drop-1;
+                System.arraycopy(pastX, start, pastX, 0, count);
+                System.arraycopy(pastY, start, pastY, 0, count);
+                System.arraycopy(pastTime, start, pastTime, 0, count);
+                i -= (drop+1);
+            }
+            pastX[i] = x;
+            pastY[i] = y;
+            pastTime[i] = time;
+            i++;
+            if (i < NUM_PAST) {
+                pastTime[i] = 0;
+            }
+        }
+
+        public void computeCurrentVelocity(int units) {
+            computeCurrentVelocity(units, Float.MAX_VALUE);
+        }
+
+        public void computeCurrentVelocity(int units, float maxVelocity) {
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            final long[] pastTime = mPastTime;
+
+            final float oldestX = pastX[0];
+            final float oldestY = pastY[0];
+            final long oldestTime = pastTime[0];
+            float accumX = 0;
+            float accumY = 0;
+            int N=0;
+            while (N < NUM_PAST) {
+                if (pastTime[N] == 0) {
+                    break;
+                }
+                N++;
+            }
+
+            for (int i=1; i < N; i++) {
+                final int dur = (int)(pastTime[i] - oldestTime);
+                if (dur == 0) continue;
+                float dist = pastX[i] - oldestX;
+                float vel = (dist/dur) * units;   // pixels/frame.
+                if (accumX == 0) accumX = vel;
+                else accumX = (accumX + vel) * .5f;
+
+                dist = pastY[i] - oldestY;
+                vel = (dist/dur) * units;   // pixels/frame.
+                if (accumY == 0) accumY = vel;
+                else accumY = (accumY + vel) * .5f;
+            }
+            mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+                    : Math.min(accumX, maxVelocity);
+            mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+                    : Math.min(accumY, maxVelocity);
+        }
+
+        public float getXVelocity() {
+            return mXVelocity;
+        }
+
+        public float getYVelocity() {
+            return mYVelocity;
+        }
+    }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/EditableAccommodatingLatinIMETypeNullIssues.java b/backends/platform/android/org/scummvm/scummvm/EditableAccommodatingLatinIMETypeNullIssues.java
index 43e04f8f72..b4a9357590 100644
--- a/backends/platform/android/org/scummvm/scummvm/EditableAccommodatingLatinIMETypeNullIssues.java
+++ b/backends/platform/android/org/scummvm/scummvm/EditableAccommodatingLatinIMETypeNullIssues.java
@@ -8,7 +8,7 @@ public class EditableAccommodatingLatinIMETypeNullIssues extends SpannableString
 	  }
 
 	//This character must be ignored by your onKey() code.
-	public static final CharSequence ONE_UNPROCESSED_CHARACTER = "/";
+	public static final CharSequence ONE_UNPROCESSED_CHARACTER = "\\";
 
 	@Override
 	public SpannableStringBuilder replace(final int spannableStringStart, final int spannableStringEnd, CharSequence replacementSequence, int replacementStart, int replacementEnd) {
@@ -22,7 +22,7 @@ public class EditableAccommodatingLatinIMETypeNullIssues extends SpannableString
 		super.replace(0, length(), "", 0, 0);
 
 		//We DO care about preserving the new stuff that is replacing the stuff in the
-		// editable, because this stuff might be sent to us as a keydown event.  So, we
+		// editable, because this stuff might be sent to us as a keyDown event.  So, we
 		// insert the new stuff (typically, a single character) into the now-empty editable,
 		// and return the result to the caller.
 		return super.replace(0, 0, replacementSequence, replacementStart, replacementEnd);
diff --git a/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
index 2f686aea26..4eaf84cd20 100644
--- a/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
+++ b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
@@ -6,6 +6,7 @@ import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -35,6 +36,89 @@ public class EditableSurfaceView extends SurfaceView {
 		_context = context;
 	}
 
+	@Override
+	public boolean onKeyDown(int keyCode, final KeyEvent event) {
+		Log.d(ScummVM.LOG_TAG, "onKeyDown - EditableSurface!!!"); // Called
+		if( keyCode == KeyEvent.KEYCODE_BACK ) {
+
+			if( ScummVMActivity.keyboardWithoutTextInputShown ) {
+				return true;
+			}
+		}
+
+		if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+			// Don't handle these
+			return false;
+		}
+		// Let our event manager handle it (ScummVMEventsBase class)
+		return super.dispatchKeyEvent(event);
+		//return false;
+
+		// This did not work
+		//return super.onKeyDown(keyCode, event);
+	}
+
+	@Override
+	public boolean onKeyUp(int keyCode, final KeyEvent event) {
+		Log.d(ScummVM.LOG_TAG, "onKeyUp - EditableSurface!!!");
+		if( keyCode == KeyEvent.KEYCODE_BACK ) {
+
+			if( ScummVMActivity.keyboardWithoutTextInputShown ) {
+				((ScummVMActivity) _context).showScreenKeyboardWithoutTextInputField(0); // Hide keyboard
+				return true;
+			}
+		}
+
+		if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+			// Don't handle these
+			return false;
+		}
+		// Let our event manager handle it (ScummVMEventsBase class)
+		return super.dispatchKeyEvent(event);
+		//return false;
+
+		// This did not work
+		//return super.onKeyUp(keyCode, event);
+	}
+
+	@Override
+	public boolean onTouchEvent(final MotionEvent event)  {
+		if( ScummVMActivity.keyboardWithoutTextInputShown && ((ScummVMActivity) _context)._screenKeyboard != null &&
+			((ScummVMActivity) _context)._screenKeyboard.getY() <= event.getY() ) {
+			event.offsetLocation(-((ScummVMActivity) _context)._screenKeyboard.getX(), -((ScummVMActivity) _context)._screenKeyboard.getY());
+			((ScummVMActivity) _context)._screenKeyboard.onTouchEvent(event);
+			return true;
+		}
+
+		if( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH ) {
+			if (getX() != 0) {
+				event.offsetLocation(-getX(), -getY());
+			}
+		}
+
+		// TODO what is this for?
+		//DifferentTouchInput.touchInput.process(event);
+
+//		// Despite the LINT warning "ScummVMEvents#onTouch should call View#performClick when a click is detected"
+//		// we deal with this in our touch event handler (in ScummVMEventsBase class
+//		switch (event.getAction()) {
+//			case MotionEvent.ACTION_UP:
+//				performClick();
+//				break;
+//			case MotionEvent.ACTION_DOWN:
+//				// fall through
+//			default:
+//				break;
+//		}
+
+//		super.onTouchEvent(event);
+		return true;
+		// This causes a crash if we dispatch to super
+		// Let our event manager handle it (ScummVMEvents class)
+//		return super.dispatchTouchEvent(event);
+	}
+
+
 	// Deal with LINT warning: Custom view `SurfaceView` has setOnTouchListener called on it but does not override performClick (in ScummVMActivity.java)
 	@Override
 	public boolean performClick() {
@@ -307,4 +391,32 @@ public class EditableSurfaceView extends SurfaceView {
 	public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) {
 		return PointerIcon.getSystemIcon(_context, PointerIcon.TYPE_NULL);
 	}
+
+	public void captureMouse(boolean capture) {
+		final boolean bGlobalsHideSystemMousePointer = true;
+		if (capture) {
+			setFocusableInTouchMode(true);
+			setFocusable(true);
+			requestFocus();
+			if (bGlobalsHideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ) {
+				postDelayed( new Runnable() {
+					public void run()
+					{
+						Log.v(ScummVM.LOG_TAG, "captureMouse::requestPointerCapture() delayed");
+						requestPointerCapture();
+					}
+				}, 50 );
+			}
+		} else {
+			if (bGlobalsHideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ) {
+				postDelayed( new Runnable() {
+					public void run() {
+						Log.v(ScummVM.LOG_TAG, "captureMouse::releasePointerCapture()");
+						releasePointerCapture();
+					}
+				}, 50 );
+			}
+		}
+	}
+
 }
diff --git a/backends/platform/android/org/scummvm/scummvm/MouseHelper.java b/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
index 6ac125d2d2..4f54708e2d 100644
--- a/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
+++ b/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
@@ -11,8 +11,8 @@ import android.view.View;
  * Contains helper methods for mouse/hover events that were introduced in Android 4.0.
  */
 public class MouseHelper {
-	private View.OnHoverListener _listener;
-	private ScummVM _scummvm;
+	private final View.OnHoverListener _listener;
+	private final ScummVM _scummvm;
 	private boolean _rmbPressed;
 	private boolean _lmbPressed;
 	private boolean _mmbPressed;
@@ -110,7 +110,7 @@ public class MouseHelper {
 		boolean lmbDown = (buttonState & MotionEvent.BUTTON_PRIMARY) == MotionEvent.BUTTON_PRIMARY;
 
 		if (!hover && e.getAction() != MotionEvent.ACTION_UP && buttonState == 0) {
-			// On some device types, ButtonState is 0 even when tapping on the touchpad or using the stylus on the screen etc.
+			// On some device types, ButtonState is 0 even when tapping on the touch-pad or using the stylus on the screen etc.
 			lmbDown = true;
 		}
 
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index f1a23c49e7..f37cbfd5e2 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -11,6 +11,8 @@ import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.graphics.Rect;
+//import android.inputmethodservice.Keyboard;
+//import android.inputmethodservice.KeyboardView;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.net.wifi.WifiInfo;
@@ -21,14 +23,17 @@ import android.os.Environment;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.SurfaceHolder;
-import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Toast;
 
@@ -49,6 +54,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.TreeSet;
 
 //import android.os.Environment;
 //import java.util.List;
@@ -92,12 +98,280 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		}
 	}
 
+	//
+	// --------------------------------------------------------------------------------------------------------------------------------------------
+	// Code for emulated in-app keyboard largely copied
+	// from https://github.com/pelya/commandergenius/tree/sdl_android/project
+	//
+	FrameLayout _videoLayout = null;
+
+	private EditableSurfaceView _main_surface = null;
+	private ImageView _toggleKeyboardBtnIcon = null;
+
+	public View _screenKeyboard = null;
+	static boolean keyboardWithoutTextInputShown = false;
+
+//	boolean _isPaused = false;
+	private InputMethodManager _inputManager = null;
+
+	private final int[][] TextInputKeyboardList =
+	{
+		{ 0, R.xml.qwerty },
+		{ 0, R.xml.qwerty_shift },
+		{ 0, R.xml.qwerty_alt },
+		{ 0, R.xml.qwerty_alt_shift }
+	};
+
+	public void showScreenKeyboardWithoutTextInputField(final int keyboard) {
+		if (_main_surface != null) {
+			_inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+			if (!keyboardWithoutTextInputShown) {
+				keyboardWithoutTextInputShown = true;
+				runOnUiThread(new Runnable() {
+					public void run() {
+						_main_surface.captureMouse(false);
+						if (keyboard == 0) {
+							// TODO do we need SHOW_FORCED HERE?
+							//_inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+							//_inputManager.showSoftInput(_main_surface, InputMethodManager.SHOW_FORCED);
+
+							_inputManager.toggleSoftInputFromWindow(_main_surface.getWindowToken(), InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
+							_inputManager.showSoftInput(_main_surface, InputMethodManager.SHOW_IMPLICIT);
+							getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+						} else {
+							if (_screenKeyboard != null) {
+								return;
+							}
+							class BuiltInKeyboardView extends CustomKeyboardView {
+
+								public boolean shift = false;
+								public boolean alt = false;
+								public final TreeSet<Integer> stickyKeys = new TreeSet<>();
+
+								public BuiltInKeyboardView(Context context, android.util.AttributeSet attrs) {
+									super(context, attrs);
+								}
+
+								public boolean onKeyDown(int key, final KeyEvent event) {
+									Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - onKeyDown()" );
+									return false;
+								}
+
+								public boolean onKeyUp(int key, final KeyEvent event) {
+									Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - onKeyUp()" );
+									return false;
+								}
+
+								public void ChangeKeyboard() {
+									// Called when bringing up the keyboard
+									// or pressing one of the special keyboard keys that change the layout (eg "123...")
+									//
+									int idx = (shift ? 1 : 0) + (alt ? 2 : 0);
+									setKeyboard(new CustomKeyboard(ScummVMActivity.this, TextInputKeyboardList[idx][keyboard]));
+									setPreviewEnabled(false);
+									setProximityCorrectionEnabled(false);
+									for (CustomKeyboard.CustomKey k: getKeyboard().getKeys()) {
+										if (stickyKeys.contains(k.codes[0])) {
+											k.on = true;
+											invalidateAllKeys();
+										}
+									}
+								}
+							}
+
+							final BuiltInKeyboardView builtinKeyboard = new BuiltInKeyboardView(ScummVMActivity.this, null);
+							builtinKeyboard.setAlpha(0.7f);
+							builtinKeyboard.ChangeKeyboard();
+							builtinKeyboard.setOnKeyboardActionListener(new CustomKeyboardView.OnKeyboardActionListener() {
+
+								public void onPress(int key) {
+									Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - onPress key: " + key ); // CALLED
+									if (key == KeyEvent.KEYCODE_BACK) {
+										return;
+									}
+
+									if (key < 0) {
+										return;
+									}
+
+									for (CustomKeyboard.CustomKey k: builtinKeyboard.getKeyboard().getKeys()) {
+										if (k.sticky && key == k.codes[0])
+											return;
+									}
+
+									if (key > 100000) {
+										key -= 100000;
+										_main_surface.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT));
+									}
+									_main_surface.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key)); // calls onKeyDown - EditableSurface!!!
+								}
+
+								public void onRelease(int key) {
+									Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - onRelease key: " + key );
+									if (key == KeyEvent.KEYCODE_BACK) {
+										builtinKeyboard.setOnKeyboardActionListener(null);
+										showScreenKeyboardWithoutTextInputField(0); // Hide keyboard
+										return;
+									}
+
+									if (key == CustomKeyboard.KEYCODE_SHIFT) {
+										builtinKeyboard.shift = ! builtinKeyboard.shift;
+										if (builtinKeyboard.shift && !builtinKeyboard.alt)
+											_main_surface.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT));
+										else
+											_main_surface.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT));
+										builtinKeyboard.ChangeKeyboard();
+										return;
+									}
+
+									if (key == CustomKeyboard.KEYCODE_ALT) {
+										builtinKeyboard.alt = ! builtinKeyboard.alt;
+										if (builtinKeyboard.alt)
+											_main_surface.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT));
+										else
+											builtinKeyboard.shift = false;
+										builtinKeyboard.ChangeKeyboard();
+										return;
+									}
+
+									if (key < 0) {
+										return;
+									}
+
+									for (CustomKeyboard.CustomKey k: builtinKeyboard.getKeyboard().getKeys()) {
+										if (k.sticky && key == k.codes[0]) {
+											if (k.on) {
+												builtinKeyboard.stickyKeys.add(key);
+												_main_surface.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key));
+											} else {
+												builtinKeyboard.stickyKeys.remove(key);
+												_main_surface.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key));
+											}
+											return;
+										}
+									}
+
+									boolean shifted = false;
+									if (key > 100000) {
+										key -= 100000;
+										shifted = true;
+									}
+
+									_main_surface.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key));
+
+									if (shifted) {
+										_main_surface.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT));
+										builtinKeyboard.stickyKeys.remove(KeyEvent.KEYCODE_SHIFT_LEFT);
+										for (CustomKeyboard.CustomKey k: builtinKeyboard.getKeyboard().getKeys())
+										{
+											if (k.sticky && k.codes[0] == KeyEvent.KEYCODE_SHIFT_LEFT && k.on)
+											{
+												k.on = false;
+												builtinKeyboard.invalidateAllKeys();
+											}
+										}
+									}
+								}
+
+								public void onText(CharSequence p1) {}
+								public void swipeLeft() {}
+								public void swipeRight() {}
+								public void swipeDown() {}
+								public void swipeUp() {}
+								public void onKey(int p1, int[] p2) {}
+							});
+
+							_screenKeyboard = builtinKeyboard;
+							// TODO better to have specific dimensions in dp and not adjusted to parent
+							//		it may resolve the issue of resizing the keyboard wrongly (smaller) when returning to the suspended Activity in low resolution
+							FrameLayout.LayoutParams sKeyboardLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+
+							_videoLayout.addView(_screenKeyboard, sKeyboardLayout);
+							_videoLayout.bringChildToFront(_screenKeyboard);
+							Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 005" );
+						}
+					}
+				});
+			} else {
+				keyboardWithoutTextInputShown = false;
+				runOnUiThread(new Runnable() {
+					public void run() {
+						if (_screenKeyboard != null ) {
+							_videoLayout.removeView(_screenKeyboard);
+							_screenKeyboard = null;
+						}
+						getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+						// TODO do we need this instead?
+						// _inputManager.hideSoftInputFromWindow(_main_surface.getWindowToken(), 0);
+						_inputManager.hideSoftInputFromWindow(_main_surface.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
+
+						DimSystemStatusBar.get().dim(_videoLayout);
+						//DimSystemStatusBar.get().dim(_main_surface);
+						_main_surface.captureMouse(true);
+					}
+				});
+			}
+			// TODO Do we need to inform native ScummVM code of keyboard shown state?
+//			_main_surface.nativeScreenKeyboardShown( keyboardWithoutTextInputShown ? 1 : 0 );
+		}
+	}
+
+	public void showScreenKeyboard() {
+		final boolean bGlobalsCompatibilityHacksTextInputEmulatesHwKeyboard = true;
+		final int dGlobalsTextInputKeyboard = 1;
+		if (_main_surface != null) {
+
+			if (bGlobalsCompatibilityHacksTextInputEmulatesHwKeyboard) {
+				showScreenKeyboardWithoutTextInputField(dGlobalsTextInputKeyboard);
+				Log.d(ScummVM.LOG_TAG, "showScreenKeyboard - showScreenKeyboardWithoutTextInputField()");
+				_main_surface.captureMouse(false);
+				return;
+			}
+			Log.d(ScummVM.LOG_TAG, "showScreenKeyboard: YOU SHOULD NOT SEE ME!!!");
+
+//			// TODO redundant ?
+//			if (_screenKeyboard != null) {
+//				return;
+//			}
+//
+		}
+	}
+
+	public void hideScreenKeyboard() {
+
+		final int dGlobalsTextInputKeyboard = 1;
+		if (_main_surface != null) {
+			if (keyboardWithoutTextInputShown) {
+				showScreenKeyboardWithoutTextInputField(dGlobalsTextInputKeyboard);
+				_main_surface.captureMouse(true);
+			}
+		}
+	}
+
+	public void toggleScreenKeyboard() {
+		if (isScreenKeyboardShown()) {
+			hideScreenKeyboard();
+		} else {
+			showScreenKeyboard();
+		}
+	}
+
+	public boolean isScreenKeyboardShown()
+	{
+		return _screenKeyboard != null;
+	}
+
+	//
+	// END OF new screenKeyboardCode
+	// ---------------------------------------------------------------------------------------------------------------------------
+	//
+
 	public final View.OnClickListener keyboardBtnOnClickListener = new View.OnClickListener() {
 		@Override
 		public void onClick(View v) {
 			runOnUiThread(new Runnable() {
 				public void run() {
-					toggleKeyboard();
+					toggleScreenKeyboard();
 				}
 			});
 		}
@@ -188,7 +462,12 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		protected void showVirtualKeyboard(final boolean enable) {
 			runOnUiThread(new Runnable() {
 				public void run() {
-					showKeyboard(enable);
+					//showKeyboard(enable);
+					if (enable) {
+						showScreenKeyboard();
+					} else {
+						hideScreenKeyboard();
+					}
 				}
 			});
 		}
@@ -197,7 +476,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		protected void showKeyboardControl(final boolean enable) {
 			runOnUiThread(new Runnable() {
 				public void run() {
-					showKeyboardView(enable);
+					showToggleKeyboardBtnIcon(enable);
 				}
 			});
 		}
@@ -254,14 +533,41 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 
 		hideSystemUI();
 
+		_videoLayout = new FrameLayout(this);
+		SetLayerType.get().setLayerType(_videoLayout);
+		setContentView(_videoLayout);
+		_videoLayout.setFocusable(true);
+		_videoLayout.setFocusableInTouchMode(true);
+		_videoLayout.requestFocus();
+
+		_main_surface = new EditableSurfaceView(this);
+		SetLayerType.get().setLayerType(_main_surface);
+
+		_videoLayout.addView(_main_surface, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+
+		_toggleKeyboardBtnIcon = new ImageView(this);
+		_toggleKeyboardBtnIcon.setImageResource(R.drawable.ic_action_keyboard);
+		FrameLayout.LayoutParams keybrdBtnlayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.TOP | Gravity.END);
+		keybrdBtnlayout.setMarginEnd(15);
+		keybrdBtnlayout.topMargin = 15;
+		keybrdBtnlayout.rightMargin = 15;
+		_videoLayout.addView(_toggleKeyboardBtnIcon, keybrdBtnlayout);
+		_videoLayout.bringChildToFront(_toggleKeyboardBtnIcon);
+
+		_main_surface.captureMouse(true);
+		// REDUNDANT?
+		if ( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N ) {
+			_main_surface.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, android.view.PointerIcon.TYPE_NULL));
+		}
+
+		// TODO is this redundant since we call hideSystemUI() ?
+		DimSystemStatusBar.get().dim(_videoLayout);
+
 		setVolumeControlStream(AudioManager.STREAM_MUSIC);
 
-		setContentView(R.layout.main);
+		// TODO needed?
 		takeKeyEvents(true);
 
-		EditableSurfaceView main_surface = findViewById(R.id.main_surface);
-
-		main_surface.requestFocus();
 		_clipboardManager = (android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
 		_currentScummVMVersion = new Version(BuildConfig.VERSION_NAME);
 		Log.d(ScummVM.LOG_TAG, "Current ScummVM version running is: " + _currentScummVMVersion.getDescription() + " (" + _currentScummVMVersion.get() + ")");
@@ -279,7 +585,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		//                            so app's internal space (which would be deleted on uninstall) was set as WORLD_READABLE which is no longer supported in newer versions of Android API
 		//                            In newer APIs we can set that path as Context.MODE_PRIVATE which is the default - but this makes the files inaccessible to other apps
 
-		_scummvm = new MyScummVM(main_surface.getHolder());
+		_scummvm = new MyScummVM(_main_surface.getHolder());
 
 		//
 		// seekAndInitScummvmConfiguration() returns false if something went wrong
@@ -308,7 +614,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			Log.d(ScummVM.LOG_TAG, "Hover available: " + _hoverAvailable);
 			if (_hoverAvailable) {
 				_mouseHelper = new MouseHelper(_scummvm);
-				_mouseHelper.attach(main_surface);
+				_mouseHelper.attach(_main_surface);
 			}
 
 			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
@@ -318,13 +624,14 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			}
 
 			// On screen button listener
-			findViewById(R.id.show_keyboard).setOnClickListener(keyboardBtnOnClickListener);
+			//findViewById(R.id.show_keyboard).setOnClickListener(keyboardBtnOnClickListener);
+			_toggleKeyboardBtnIcon.setOnClickListener(keyboardBtnOnClickListener);
 
-			// Keyboard visibility listener
+			// Keyboard visibility listener - mainly to hide system UI if keyboard is shown and we return from Suspend to the Activity
 			setKeyboardVisibilityListener(this);
 
-			main_surface.setOnKeyListener(_events);
-			main_surface.setOnTouchListener(_events);
+			_main_surface.setOnKeyListener(_events);
+			_main_surface.setOnTouchListener(_events);
 
 			_scummvm_thread = new Thread(_scummvm, "ScummVM");
 			_scummvm_thread.start();
@@ -342,6 +649,8 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 	public void onResume() {
 		Log.d(ScummVM.LOG_TAG, "onResume");
 
+//		_isPaused = false;
+
 		super.onResume();
 
 		if (_scummvm != null)
@@ -353,6 +662,8 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 	public void onPause() {
 		Log.d(ScummVM.LOG_TAG, "onPause");
 
+//		_isPaused = true;
+
 		super.onPause();
 
 		if (_scummvm != null)
@@ -387,7 +698,10 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			_scummvm = null;
 		}
 
-		showKeyboardView(false);
+		if (isScreenKeyboardShown()) {
+			hideScreenKeyboard();
+		}
+		showToggleKeyboardBtnIcon(false);
 	}
 
 
@@ -463,7 +777,6 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		}
 	}
 
-
 	// TODO setSystemUiVisibility is introduced in API 11 and deprecated in API 30 - When we move to API 30 we will have to replace this code
 	//	https://developer.android.com/training/system-ui/immersive.html#java
 	//
@@ -499,53 +812,61 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 //		    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
 //	}
 
-	// Show or hide the Android keyboard.
-	// Called by the override of showVirtualKeyboard()
-	@TargetApi(Build.VERSION_CODES.CUPCAKE)
-	private void showKeyboard(boolean show) {
-		SurfaceView main_surface = findViewById(R.id.main_surface);
-		InputMethodManager imm = (InputMethodManager)
-			getSystemService(INPUT_METHOD_SERVICE);
-
-		if (show) {
-			imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT);
-		} else  {
-			imm.hideSoftInputFromWindow(main_surface.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
-		}
-	}
-
-	// Toggle showing or hiding the virtual keyboard.
-	// Called by keyboardBtnOnClickListener()
-	@TargetApi(Build.VERSION_CODES.CUPCAKE)
-	private void toggleKeyboard() {
-		SurfaceView main_surface = findViewById(R.id.main_surface);
-		InputMethodManager imm = (InputMethodManager)
-			getSystemService(INPUT_METHOD_SERVICE);
-
-		imm.toggleSoftInputFromWindow(main_surface.getWindowToken(),
-			InputMethodManager.SHOW_IMPLICIT,
-			InputMethodManager.HIDE_IMPLICIT_ONLY);
-	}
+//	// Show or hide the Android keyboard.
+//	// Called by the override of showVirtualKeyboard()
+//	@TargetApi(Build.VERSION_CODES.CUPCAKE)
+//	private void showKeyboard(boolean show) {
+//		//SurfaceView main_surface = findViewById(R.id.main_surface);
+//		if (_main_surface != null) {
+//
+//			InputMethodManager imm = (InputMethodManager)
+//				getSystemService(INPUT_METHOD_SERVICE);
+//
+//			if (show) {
+//				imm.showSoftInput(_main_surface, InputMethodManager.SHOW_IMPLICIT);
+//			} else {
+//				imm.hideSoftInputFromWindow(_main_surface.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
+//			}
+//		}
+//	}
+//
+//	// Toggle showing or hiding the virtual keyboard.
+//	// Called by keyboardBtnOnClickListener()
+//	@TargetApi(Build.VERSION_CODES.CUPCAKE)
+//	private void toggleKeyboard() {
+//		//SurfaceView main_surface = findViewById(R.id.main_surface);
+//		if (_main_surface != null ) {
+//			InputMethodManager imm = (InputMethodManager)
+//				getSystemService(INPUT_METHOD_SERVICE);
+//
+//			imm.toggleSoftInputFromWindow(_main_surface.getWindowToken(),
+//				InputMethodManager.SHOW_IMPLICIT,
+//				InputMethodManager.HIDE_IMPLICIT_ONLY);
+//		}
+//	}
 
 	// Show or hide the semi-transparent keyboard btn (which is used to explicitly bring up the android keyboard).
 	// Called by the override of showKeyboardControl()
-	private void showKeyboardView(boolean show) {
-		ImageView keyboardBtn = findViewById(R.id.show_keyboard);
-
-		if (show) {
-			keyboardBtn.setVisibility(View.VISIBLE);
-		} else {
-			keyboardBtn.setVisibility(View.GONE);
+	private void showToggleKeyboardBtnIcon(boolean show) {
+		//ImageView keyboardBtn = findViewById(R.id.show_keyboard);
+		if (_toggleKeyboardBtnIcon != null ) {
+			if (show) {
+				_toggleKeyboardBtnIcon.setVisibility(View.VISIBLE);
+			} else {
+				_toggleKeyboardBtnIcon.setVisibility(View.GONE);
+			}
 		}
 	}
 
 	private void showMouseCursor(boolean show) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 			// Android N (Nougat) is Android 7.0
-			SurfaceView main_surface = findViewById(R.id.main_surface);
-			int type = show ? PointerIcon.TYPE_DEFAULT : PointerIcon.TYPE_NULL;
-			// https://stackoverflow.com/a/55482761
-			main_surface.setPointerIcon(PointerIcon.getSystemIcon(this, type));
+			//SurfaceView main_surface = findViewById(R.id.main_surface);
+			if (_main_surface != null) {
+				int type = show ? PointerIcon.TYPE_DEFAULT : PointerIcon.TYPE_NULL;
+				// https://stackoverflow.com/a/55482761
+				_main_surface.setPointerIcon(PointerIcon.getSystemIcon(this, type));
+			}
 		} else {
 			/* Currently hiding the system mouse cursor is only
 			   supported on OUYA.  If other systems provide similar
@@ -562,29 +883,31 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 	// https://stackoverflow.com/a/36259261
 	private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
 		final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
-		parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
-
-			private boolean alreadyOpen;
-			private final int defaultKeyboardHeightDP = 100;
-			private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
-			private final Rect rect = new Rect();
-
-			@TargetApi(Build.VERSION_CODES.CUPCAKE)
-			@Override
-			public void onGlobalLayout() {
-				int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
-				parentView.getWindowVisibleDisplayFrame(rect);
-				int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
-				boolean isShown = heightDiff >= estimatedKeyboardHeight;
-
-				if (isShown == alreadyOpen) {
-					Log.i("Keyboard state", "Ignoring global layout change...");
-					return;
+		if (parentView != null) {
+			parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+
+				private boolean alreadyOpen;
+				private final int defaultKeyboardHeightDP = 100;
+				private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
+				private final Rect rect = new Rect();
+
+				@TargetApi(Build.VERSION_CODES.CUPCAKE)
+				@Override
+				public void onGlobalLayout() {
+					int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
+					parentView.getWindowVisibleDisplayFrame(rect);
+					int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
+					boolean isShown = heightDiff >= estimatedKeyboardHeight;
+
+					if (isShown == alreadyOpen) {
+						Log.i(ScummVM.LOG_TAG, "Keyboard state:: ignoring global layout change...");
+						return;
+					}
+					alreadyOpen = isShown;
+					onKeyboardVisibilityListener.onVisibilityChanged(isShown);
 				}
-				alreadyOpen = isShown;
-				onKeyboardVisibilityListener.onVisibilityChanged(isShown);
-			}
-		});
+			});
+		}
 	}
 
 	@Override
@@ -699,7 +1022,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		//
 		// If the dir can't be reached, it will print a warning!
 		//
-		//   Log.w(TAG, "Failed to ensure directory: " + dir);
+		//   Log.w(ScummVM.LOG_TAG, "Failed to ensure directory: " + dir);
 		//   dir = null;
 		//
 		// So, if your device has two sdcard paths, it will produce two dirs. If one is not available, the warning will come up.
@@ -1384,5 +1707,76 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			}
 		}
 	}
+} // end of ScummVMActivity
+
+// *** HONEYCOMB / ICS FIX FOR FULLSCREEN MODE, by lmak ***
+// TODO DimSystemStatusBar may be redundant for us
+abstract class DimSystemStatusBar {
+
+	final boolean bGlobalsImmersiveMode = true;
+
+	public static DimSystemStatusBar get() {
+		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+			return DimSystemStatusBarHoneycomb.Holder.sInstance;
+		} else {
+			return DimSystemStatusBarDummy.Holder.sInstance;
+		}
+	}
+
+	public abstract void dim(final View view);
+
+	private static class DimSystemStatusBarHoneycomb extends DimSystemStatusBar {
+		private static class Holder {
+			private static final DimSystemStatusBarHoneycomb sInstance = new DimSystemStatusBarHoneycomb();
+		}
+
+		public void dim(final View view) {
+			if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && bGlobalsImmersiveMode) {
+				// Immersive mode, I already hear curses when system bar reappears mid-game from the slightest swipe at the bottom of the screen
+				view.setSystemUiVisibility(android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | android.view.View.SYSTEM_UI_FLAG_FULLSCREEN);
+			} else {
+				view.setSystemUiVisibility(android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE);
+			}
+		}
+	}
 
+	private static class DimSystemStatusBarDummy extends DimSystemStatusBar {
+		private static class Holder {
+			private static final DimSystemStatusBarDummy sInstance = new DimSystemStatusBarDummy();
+		}
+
+		public void dim(final View view) { }
+	}
+}
+
+abstract class SetLayerType {
+
+	public static SetLayerType get() {
+		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+			return SetLayerTypeHoneycomb.Holder.sInstance;
+		} else {
+			return SetLayerTypeDummy.Holder.sInstance;
+		}
+	}
+
+	public abstract void setLayerType(final View view);
+
+	private static class SetLayerTypeHoneycomb extends SetLayerType {
+		private static class Holder {
+			private static final SetLayerTypeHoneycomb sInstance = new SetLayerTypeHoneycomb();
+		}
+
+		public void setLayerType(final View view) {
+			view.setLayerType(android.view.View.LAYER_TYPE_NONE, null);
+			//view.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
+		}
+	}
+
+	private static class SetLayerTypeDummy extends SetLayerType {
+		private static class Holder {
+			private static final SetLayerTypeDummy sInstance = new SetLayerTypeDummy();
+		}
+
+		public void setLayerType(final View view) { }
+	}
 }
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
index efa9395b1f..3ac933c31d 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
@@ -4,6 +4,7 @@ import android.os.Handler;
 import android.os.Message;
 import android.content.Context;
 //import android.util.Log;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.KeyCharacterMap;
 import android.view.MotionEvent;
@@ -11,7 +12,7 @@ import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.GestureDetector;
 import android.view.InputDevice;
-import android.view.inputmethod.InputMethodManager;
+//import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.NonNull;
 
@@ -103,13 +104,14 @@ public class ScummVMEventsBase implements
 
 	private void handleEVHMessage(final Message msg) {
 		if (msg.what == MSG_SMENU_LONG_PRESS) {
-			// this displays the android keyboard (see showVirtualKeyboard() in ScummVMActivity.java)
+			// this toggles the android keyboard (see showVirtualKeyboard() in ScummVMActivity.java)
 			// when menu key is long-pressed
-			InputMethodManager imm = (InputMethodManager)
-				_context.getSystemService(Context.INPUT_METHOD_SERVICE);
-
-			if (imm != null)
-				imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+//			InputMethodManager imm = (InputMethodManager)
+//				_context.getSystemService(Context.INPUT_METHOD_SERVICE);
+//
+//			if (imm != null)
+//				imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+			((ScummVMActivity) _context).toggleScreenKeyboard();
 		} else if (msg.what == MSG_SBACK_LONG_PRESS) {
 			_scummvm.pushEvent(JE_SYS_KEY, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU, 0, 0, 0, 0);
 			_scummvm.pushEvent(JE_SYS_KEY, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU, 0, 0, 0, 0);
@@ -142,6 +144,7 @@ public class ScummVMEventsBase implements
 	// OnKeyListener
 	@Override
 	final public boolean onKey(View v, int keyCode, KeyEvent e) {
+		//Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onKEY");
 		final int action = e.getAction();
 
 		if (e.getUnicodeChar() == (int)EditableAccommodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER.charAt(0)) {
@@ -287,6 +290,7 @@ public class ScummVMEventsBase implements
 	// OnTouchListener
 	@Override
 	final public boolean onTouch(View v, MotionEvent e) {
+		//Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onTOUCH");
 
 		if (_mouseHelper != null) {
 			boolean isMouse = MouseHelper.isMouse(e);
@@ -324,6 +328,7 @@ public class ScummVMEventsBase implements
 	// OnGestureListener
 	@Override
 	final public boolean onDown(MotionEvent e) {
+		//Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onDONW");
 		_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
 		return true;
 	}
diff --git a/backends/platform/android/org/scummvm/scummvm/SplashActivity.java b/backends/platform/android/org/scummvm/scummvm/SplashActivity.java
index 2eb84eb65e..cce03c9db4 100644
--- a/backends/platform/android/org/scummvm/scummvm/SplashActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/SplashActivity.java
@@ -54,8 +54,8 @@ public class SplashActivity extends Activity {
 			int numOfReqPermsGranted = 0;
 			// If request is cancelled, the result arrays are empty.
 			if (grantResults.length > 0) {
-				for (int iterGrantResult: grantResults) {
-					if (iterGrantResult == PackageManager.PERMISSION_GRANTED) {
+				for (int iterateGrantResult: grantResults) {
+					if (iterateGrantResult == PackageManager.PERMISSION_GRANTED) {
 						Log.i(ScummVM.LOG_TAG, permissions[0] + " permission was granted at Runtime");
 						++numOfReqPermsGranted;
 					} else {
diff --git a/dists/android/res/drawable-hdpi/btn_close_normal.png b/dists/android/res/drawable-hdpi/btn_close_normal.png
new file mode 100755
index 0000000000..47f11e5bf6
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_close_normal.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_close_pressed.png b/dists/android/res/drawable-hdpi/btn_close_pressed.png
new file mode 100755
index 0000000000..5b96b4e091
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_close_pressed.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_close_selected.png b/dists/android/res/drawable-hdpi/btn_close_selected.png
new file mode 100755
index 0000000000..e27d6847e7
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_close_selected.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_normal.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000000..42c7c146d6
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal.9.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000000..01e2506b0f
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000000..83c6eb3fc1
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
new file mode 100644
index 0000000000..e047eaff15
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed.9.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000000..218a2d29ee
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png differ
diff --git a/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000000..afe49512e9
Binary files /dev/null and b/dists/android/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png differ
diff --git a/dists/android/res/drawable-hdpi/keyboard_background.9.png b/dists/android/res/drawable-hdpi/keyboard_background.9.png
new file mode 100755
index 0000000000..7a03b8e4ba
Binary files /dev/null and b/dists/android/res/drawable-hdpi/keyboard_background.9.png differ
diff --git a/dists/android/res/drawable-hdpi/keyboard_key_feedback_background.9.png b/dists/android/res/drawable-hdpi/keyboard_key_feedback_background.9.png
new file mode 100755
index 0000000000..6ba42db823
Binary files /dev/null and b/dists/android/res/drawable-hdpi/keyboard_key_feedback_background.9.png differ
diff --git a/dists/android/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png b/dists/android/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000000..4d0b601099
Binary files /dev/null and b/dists/android/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png differ
diff --git a/dists/android/res/drawable-hdpi/keyboard_popup_panel_background.9.png b/dists/android/res/drawable-hdpi/keyboard_popup_panel_background.9.png
new file mode 100755
index 0000000000..8e2461b3f1
Binary files /dev/null and b/dists/android/res/drawable-hdpi/keyboard_popup_panel_background.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_close_normal.png b/dists/android/res/drawable-ldpi/btn_close_normal.png
new file mode 100755
index 0000000000..e4de0885a8
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_close_normal.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_close_pressed.png b/dists/android/res/drawable-ldpi/btn_close_pressed.png
new file mode 100755
index 0000000000..5d946bd340
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_close_pressed.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_close_selected.png b/dists/android/res/drawable-ldpi/btn_close_selected.png
new file mode 100755
index 0000000000..c1ee5a8942
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_close_selected.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_normal.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000000..69db65f2f1
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_off.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000000..37c5fedbe2
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_off.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_on.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000000..019e6f78c1
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_normal_on.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed.9.png
new file mode 100644
index 0000000000..d3827f0328
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_off.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000000..2bef004963
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_off.9.png differ
diff --git a/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_on.9.png b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000000..25daabeed0
Binary files /dev/null and b/dists/android/res/drawable-ldpi/btn_keyboard_key_pressed_on.9.png differ
diff --git a/dists/android/res/drawable-ldpi/keyboard_background.9.png b/dists/android/res/drawable-ldpi/keyboard_background.9.png
new file mode 100755
index 0000000000..06d42c0ce9
Binary files /dev/null and b/dists/android/res/drawable-ldpi/keyboard_background.9.png differ
diff --git a/dists/android/res/drawable-ldpi/keyboard_key_feedback_background.9.png b/dists/android/res/drawable-ldpi/keyboard_key_feedback_background.9.png
new file mode 100755
index 0000000000..6f936f1571
Binary files /dev/null and b/dists/android/res/drawable-ldpi/keyboard_key_feedback_background.9.png differ
diff --git a/dists/android/res/drawable-ldpi/keyboard_key_feedback_more_background.9.png b/dists/android/res/drawable-ldpi/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000000..7e81c3d433
Binary files /dev/null and b/dists/android/res/drawable-ldpi/keyboard_key_feedback_more_background.9.png differ
diff --git a/dists/android/res/drawable-ldpi/keyboard_popup_panel_background.9.png b/dists/android/res/drawable-ldpi/keyboard_popup_panel_background.9.png
new file mode 100755
index 0000000000..955fecc252
Binary files /dev/null and b/dists/android/res/drawable-ldpi/keyboard_popup_panel_background.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_close_normal.png b/dists/android/res/drawable-mdpi/btn_close_normal.png
new file mode 100755
index 0000000000..eca5828bf8
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_close_normal.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_close_pressed.png b/dists/android/res/drawable-mdpi/btn_close_pressed.png
new file mode 100755
index 0000000000..3c745bbeb2
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_close_pressed.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_close_selected.png b/dists/android/res/drawable-mdpi/btn_close_selected.png
new file mode 100755
index 0000000000..c41f039b34
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_close_selected.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_normal.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000000..7ba18dd25a
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_off.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000000..bda9b83941
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_off.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_on.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000000..0c16ed5093
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_normal_on.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed.9.png
new file mode 100644
index 0000000000..39b9314a1a
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_off.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000000..bdcf06e1b8
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_off.9.png differ
diff --git a/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_on.9.png b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000000..79621a9e63
Binary files /dev/null and b/dists/android/res/drawable-mdpi/btn_keyboard_key_pressed_on.9.png differ
diff --git a/dists/android/res/drawable-mdpi/keyboard_background.9.png b/dists/android/res/drawable-mdpi/keyboard_background.9.png
new file mode 100755
index 0000000000..1d3ce05b86
Binary files /dev/null and b/dists/android/res/drawable-mdpi/keyboard_background.9.png differ
diff --git a/dists/android/res/drawable-mdpi/keyboard_key_feedback_background.9.png b/dists/android/res/drawable-mdpi/keyboard_key_feedback_background.9.png
new file mode 100755
index 0000000000..2a80f096d2
Binary files /dev/null and b/dists/android/res/drawable-mdpi/keyboard_key_feedback_background.9.png differ
diff --git a/dists/android/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png b/dists/android/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000000..29aa285bd5
Binary files /dev/null and b/dists/android/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png differ
diff --git a/dists/android/res/drawable-mdpi/keyboard_popup_panel_background.9.png b/dists/android/res/drawable-mdpi/keyboard_popup_panel_background.9.png
new file mode 100755
index 0000000000..36d75df6f9
Binary files /dev/null and b/dists/android/res/drawable-mdpi/keyboard_popup_panel_background.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_close_normal.png b/dists/android/res/drawable-xhdpi/btn_close_normal.png
new file mode 100755
index 0000000000..2d0b0ea135
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_close_normal.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_close_pressed.png b/dists/android/res/drawable-xhdpi/btn_close_pressed.png
new file mode 100755
index 0000000000..5d9b5eeac5
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_close_pressed.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_close_selected.png b/dists/android/res/drawable-xhdpi/btn_close_selected.png
new file mode 100755
index 0000000000..1bf1740652
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_close_selected.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal.9.png
new file mode 100644
index 0000000000..1f3a6b3c08
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_off.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_off.9.png
new file mode 100644
index 0000000000..2a9b6f49ef
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_off.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_on.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_on.9.png
new file mode 100644
index 0000000000..096d6e9e9f
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_normal_on.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed.9.png
new file mode 100644
index 0000000000..20852d654b
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_off.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_off.9.png
new file mode 100644
index 0000000000..271c6b4162
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_off.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_on.9.png b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_on.9.png
new file mode 100644
index 0000000000..e72ec794fb
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/btn_keyboard_key_pressed_on.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/keyboard_background.9.png b/dists/android/res/drawable-xhdpi/keyboard_background.9.png
new file mode 100755
index 0000000000..33d8519b1b
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/keyboard_background.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/keyboard_key_feedback_background.9.png b/dists/android/res/drawable-xhdpi/keyboard_key_feedback_background.9.png
new file mode 100755
index 0000000000..6e8584bf71
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/keyboard_key_feedback_background.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png b/dists/android/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000000..d983a9521e
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png differ
diff --git a/dists/android/res/drawable-xhdpi/keyboard_popup_panel_background.9.png b/dists/android/res/drawable-xhdpi/keyboard_popup_panel_background.9.png
new file mode 100755
index 0000000000..d9f48199b8
Binary files /dev/null and b/dists/android/res/drawable-xhdpi/keyboard_popup_panel_background.9.png differ
diff --git a/dists/android/res/drawable/btn_close.xml b/dists/android/res/drawable/btn_close.xml
new file mode 100755
index 0000000000..d0b8b8bad7
--- /dev/null
+++ b/dists/android/res/drawable/btn_close.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false" android:state_focused="false"
+        android:drawable="@drawable/btn_close_normal" />
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_close_pressed" />
+
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_close_selected" />
+</selector>
diff --git a/dists/android/res/drawable/btn_keyboard_key.xml b/dists/android/res/drawable/btn_keyboard_key.xml
new file mode 100644
index 0000000000..9777d868be
--- /dev/null
+++ b/dists/android/res/drawable/btn_keyboard_key.xml
@@ -0,0 +1,34 @@
+<?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.
+	-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+<!-- Toggle keys. Use checkable/checked state. -->
+
+<item android:state_checkable="true" android:state_checked="true"
+	android:state_pressed="true"
+	android:drawable="@drawable/btn_keyboard_key_pressed_on" />
+<item android:state_checkable="true" android:state_pressed="true"
+	android:drawable="@drawable/btn_keyboard_key_pressed_off" />
+<item android:state_checkable="true" android:state_checked="true"
+	android:drawable="@drawable/btn_keyboard_key_normal_on" />
+<item android:state_checkable="true"
+	android:drawable="@drawable/btn_keyboard_key_normal_off" />
+
+<!-- Normal keys -->
+
+<item android:state_pressed="true"
+	android:drawable="@drawable/btn_keyboard_key_pressed" />
+<item
+	android:drawable="@drawable/btn_keyboard_key_normal" />
+
+</selector>
diff --git a/dists/android/res/drawable/keyboard_key_feedback.xml b/dists/android/res/drawable/keyboard_key_feedback.xml
new file mode 100755
index 0000000000..3c25611148
--- /dev/null
+++ b/dists/android/res/drawable/keyboard_key_feedback.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_long_pressable="true"
+            android:drawable="@drawable/keyboard_key_feedback_more_background" />
+
+    <item android:drawable="@drawable/keyboard_key_feedback_background" />
+</selector>
diff --git a/dists/android/res/layout/keyboard_key_preview.xml b/dists/android/res/layout/keyboard_key_preview.xml
new file mode 100755
index 0000000000..a6e096b4ef
--- /dev/null
+++ b/dists/android/res/layout/keyboard_key_preview.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" 
+    android:layout_height="80sp"
+    android:textSize="40sp"
+    android:textColor="?android:attr/textColorPrimaryInverse"
+    android:minWidth="32dip"
+    android:gravity="center"
+    android:background="@drawable/keyboard_key_feedback"
+    />
diff --git a/dists/android/res/layout/keyboard_popup_keyboard.xml b/dists/android/res/layout/keyboard_popup_keyboard.xml
new file mode 100755
index 0000000000..176ffc157f
--- /dev/null
+++ b/dists/android/res/layout/keyboard_popup_keyboard.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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="wrap_content"
+        android:orientation="horizontal"
+        android:background="@drawable/keyboard_popup_panel_background"
+        >
+
+	<!-- Excluded attribute due to error: (layout should not include itself) android:popupLayout="@layout/keyboard_popup_keyboard" -->
+	<!-- Removed attribute due to invalid for LinearLayout android:layout_alignParentBottom="true" -->
+    <org.scummvm.scummvm.CustomKeyboardView
+            android:id="@android:id/keyboardView"
+            android:background="@android:color/transparent"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:keyPreviewLayout="@layout/keyboard_key_preview"
+            android:keyTextSize="22sp"
+            />
+    <ImageButton android:id="@android:id/closeButton"
+        android:background="@android:color/transparent"
+        android:src="@drawable/btn_close"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="8dp"
+        android:clickable="true"
+		android:layout_marginLeft="8dp"
+		android:focusable="true"
+		android:contentDescription="@string/customkeyboardview_popup_close" />
+</LinearLayout>
diff --git a/dists/android/res/layout/main.xml b/dists/android/res/layout/main.xml
deleted file mode 100644
index 1d05a3b4d6..0000000000
--- a/dists/android/res/layout/main.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent">
-
-<org.scummvm.scummvm.EditableSurfaceView
-	android:id="@+id/main_surface"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent"
-	android:gravity="center"
-	android:keepScreenOn="true"
-	android:focusable="true"
-	android:focusableInTouchMode="true"
-/>
-
-<ImageView
-	android:layout_width="wrap_content"
-	android:layout_height="wrap_content"
-	android:src="@drawable/ic_action_keyboard"
-	android:id="@+id/show_keyboard"
-	android:layout_alignParentTop="true"
-	android:layout_alignParentRight="true"
-	android:layout_alignParentEnd="true"
-	android:layout_marginRight="15dp"
-	android:layout_marginEnd="15dp"
-	android:layout_marginTop="15dp"
-	android:contentDescription="@string/keyboard_toggle_btn_desc" />
-
-</RelativeLayout>
diff --git a/dists/android/res/values/attrs_min.xml b/dists/android/res/values/attrs_min.xml
new file mode 100644
index 0000000000..442c698d6f
--- /dev/null
+++ b/dists/android/res/values/attrs_min.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+
+	<!-- These are the standard attributes that make up a complete theme. -->
+    <declare-styleable name="CustomKeyboardView">
+		<!-- Default background dim amount when a menu, dialog, or something similar pops up. -->
+		<attr name="backgroundDimAmount" format="float" />
+
+        <!-- Default KeyboardView style. -->
+        <attr name="keyboardViewStyle" format="reference" />
+
+        <!-- Image for the key. This image needs to be a StateListDrawable, with the following
+             possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
+             checkable+checked+pressed. -->
+        <attr name="keyBackground" format="reference" />
+
+        <!-- Size of the text for character keys. -->
+        <attr name="keyTextSize" format="dimension" />
+
+        <!-- Size of the text for custom keys with some text and no icon. -->
+        <attr name="labelTextSize" format="dimension" />
+
+        <!-- Color to use for the label in a key. -->
+        <attr name="keyTextColor" format="color" />
+
+        <!-- Layout resource for key press feedback. -->
+        <attr name="keyPreviewLayout" format="reference" />
+
+        <!-- Vertical offset of the key press feedback from the key. -->
+        <attr name="keyPreviewOffset" format="dimension" />
+
+        <!-- Height of the key press feedback popup. -->
+        <attr name="keyPreviewHeight" format="dimension" />
+
+        <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
+        <attr name="verticalCorrection" format="dimension" />
+
+        <!-- Layout resource for popup keyboards. -->
+        <attr name="popupLayout" format="reference" />
+
+        <attr name="shadowColor" format="color" />
+
+        <attr name="shadowRadius" format="float" />
+    </declare-styleable>
+
+    <declare-styleable name="CustomKeyboardViewPreviewState">
+        <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView}
+                key preview background. -->
+        <attr name="state_long_pressable" format="boolean" />
+    </declare-styleable>
+
+    <declare-styleable name="CustomKeyboard">
+        <!-- Default width of a key, in pixels or percentage of display width. -->
+        <attr name="keyWidth" format="dimension|fraction" />
+        <!-- Default height of a key, in pixels or percentage of display width. -->
+        <attr name="keyHeight" format="dimension|fraction" />
+        <!-- Default horizontal gap between keys. -->
+        <attr name="horizontalGap" format="dimension|fraction" />
+        <!-- Default vertical gap between rows of keys. -->
+        <attr name="verticalGap" format="dimension|fraction" />
+    </declare-styleable>
+
+    <declare-styleable name="CustomKeyboard_CustomRow">
+        <!-- Row edge flags. -->
+        <attr name="rowEdgeFlags">
+            <!-- Row is anchored to the top of the keyboard. -->
+            <flag name="top" value="4" />
+            <!-- Row is anchored to the bottom of the keyboard. -->
+            <flag name="bottom" value="8" />
+        </attr>
+        <!-- Mode of the keyboard. If the mode doesn't match the
+             requested keyboard mode, the row will be skipped.  -->
+        <attr name="keyboardMode" format="reference" />
+    </declare-styleable>
+
+    <declare-styleable name="CustomKeyboard_CustomKey">
+        <!-- The unicode value or comma-separated values that this key outputs. -->
+        <attr name="codes" format="integer|string" />
+        <!-- The XML keyboard layout of any popup keyboard. -->
+        <attr name="popupKeyboard" format="reference" />
+        <!-- The characters to display in the popup keyboard. -->
+        <attr name="popupCharacters" format="string" />
+        <!-- Key edge flags. -->
+        <attr name="keyEdgeFlags">
+            <!-- Key is anchored to the left of the keyboard. -->
+            <flag name="left" value="1" />
+            <!-- Key is anchored to the right of the keyboard. -->
+            <flag name="right" value="2" />
+        </attr>
+        <!-- Whether this is a modifier key such as Alt or Shift. -->
+        <attr name="isModifier" format="boolean" />
+        <!-- Whether this is a toggle key. -->
+        <attr name="isSticky" format="boolean" />
+        <!-- Whether long-pressing on this key will make it repeat. -->
+        <attr name="isRepeatable" format="boolean" />
+        <!-- The icon to show in the popup preview. -->
+        <attr name="iconPreview" format="reference" />
+        <!-- The string of characters to output when this key is pressed. -->
+        <attr name="keyOutputText" format="string" />
+        <!-- The label to display on the key. -->
+        <attr name="keyLabel" format="string" />
+        <!-- The icon to display on the key instead of the label. -->
+        <attr name="keyIcon" format="reference" />
+        <!-- Mode of the keyboard. If the mode doesn't match the
+             requested keyboard mode, the key will be skipped. -->
+        <attr name="keyboardMode" />
+    </declare-styleable>
+</resources>
diff --git a/dists/android/res/values/colors.xml b/dists/android/res/values/colors.xml
index 825f026ce5..a147d5be92 100644
--- a/dists/android/res/values/colors.xml
+++ b/dists/android/res/values/colors.xml
@@ -11,5 +11,6 @@
 	<!-- <color name="colorAccent">#FFCC5500</color> -->
 
     <color name="colorBackground">#FFCC6600</color>
+	<!-- <color name="blurb">#FF000000</color> -->
 </resources>
 
diff --git a/dists/android/res/values/config.xml b/dists/android/res/values/config.xml
new file mode 100644
index 0000000000..116c0dcb20
--- /dev/null
+++ b/dists/android/res/values/config.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*
+* Copy from AOSP
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue". Some legacy
+     entries do not follow the convention, but all new entries should. -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+	<!-- Enables swipe versus poly-finger touch disambiguation in the KeyboardView -->
+	<bool name="config_swipeDisambiguation">true</bool>
+</resources>
diff --git a/dists/android/res/values/strings.xml b/dists/android/res/values/strings.xml
index a619c210b6..9c99e5dfb9 100644
--- a/dists/android/res/values/strings.xml
+++ b/dists/android/res/values/strings.xml
@@ -34,4 +34,23 @@
 	<string name="keyboard_toggle_btn_desc">Toggle virtual keyboard</string>
 	<!-- <string name="title_activity_splash">ScummVM Logo Activity</string> -->
 	<!-- <string name="title_activity_main">ScummVM Main Activity</string> -->
+
+	<!-- Copy from AOSP (Android Open Source Project) -->
+	<!-- CustomKeyboardView - accessibility support -->
+	<!-- Description of the Alt button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_alt">Alt</string>
+	<!-- Description of the Cancel button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_cancel">Cancel</string>
+	<!-- Description of the Delete button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_delete">Delete</string>
+	<!-- Description of the Done button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_done">Done</string>
+	<!-- Description of the Mode change button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_mode_change">Mode change</string>
+	<!-- Description of the Shift button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_shift">Shift</string>
+	<!-- Description of the Enter button in a KeyboardView. [CHAR LIMIT=NONE] -->
+	<string name="customkeyboardview_keycode_enter">Enter</string>
+	<!-- End of copy from AOSP -->
+	<string name="customkeyboardview_popup_close">Close popup</string>
 </resources>
diff --git a/dists/android/res/values/styles.xml b/dists/android/res/values/styles.xml
new file mode 100644
index 0000000000..b90927149b
--- /dev/null
+++ b/dists/android/res/values/styles.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<resources>
+	<!-- Style largely taken from AOSP (frameworks/base/core/res/values) for Widget.KeyboardView -->
+	<style name="View.CustomKeyboard" parent="android:Widget" >
+		<!-- View's background attribute (reference (a drawable) or color) -->
+		<!-- A drawable to use as the background.  This can be either a reference
+			to a full drawable resource (such as a PNG image, 9-patch,
+			XML state list description, etc), or a solid color such as "#ff000000"
+		   (black). -->
+		<!-- drawables for keyboard taken from AOSP (frameworks/base/core/res) -->
+		<item name="android:background">@drawable/keyboard_background</item>
+		<item name="keyBackground">@drawable/btn_keyboard_key</item>
+		<item name="keyTextSize">22sp</item>
+		<item name="keyTextColor">#FFFFFFFF</item>
+		<item name="keyPreviewLayout">@layout/keyboard_key_preview</item>
+		<item name="keyPreviewOffset">-12dip</item>
+		<item name="keyPreviewHeight">80dip</item>
+		<item name="labelTextSize">14sp</item>
+		<item name="popupLayout">@layout/keyboard_popup_keyboard</item>
+		<item name="verticalCorrection">-10dip</item>
+		<item name="shadowColor">#BB000000</item>
+		<item name="shadowRadius">2.75</item>
+	</style>
+</resources>
diff --git a/dists/android/res/values/themes.xml b/dists/android/res/values/themes.xml
index de8da852e5..e2a6987d63 100644
--- a/dists/android/res/values/themes.xml
+++ b/dists/android/res/values/themes.xml
@@ -3,9 +3,11 @@
 
 	<style name="AppTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen">
 		<item name="android:colorBackground">@color/colorBackground</item>
+		<item name="keyboardViewStyle">@style/View.CustomKeyboard</item>
 	</style>
 
 	<style name="SplashTheme" parent="AppTheme">
 		<item name="android:windowBackground">@drawable/splash</item>
 	</style>
+
 </resources>
diff --git a/dists/android/res/xml/qwerty.xml b/dists/android/res/xml/qwerty.xml
new file mode 100644
index 0000000000..d47c174746
--- /dev/null
+++ b/dists/android/res/xml/qwerty.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- When creating new keyboard layout, add it to TextInputKeyboardList array in ScummVMActivity.java -->
+<CustomKeyboard xmlns:scummvm="http://schemas.android.com/apk/res-auto"
+	scummvm:keyWidth="10%p"
+	scummvm:horizontalGap="0px"
+	scummvm:verticalGap="0px"
+	scummvm:keyHeight="10%p">
+	<CustomRow>
+		<CustomKey scummvm:codes="45" scummvm:keyLabel="q" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="51" scummvm:keyLabel="w" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="33" scummvm:keyLabel="e" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="46" scummvm:keyLabel="r" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="48" scummvm:keyLabel="t" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="53" scummvm:keyLabel="y" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="49" scummvm:keyLabel="u" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="37" scummvm:keyLabel="i" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="43" scummvm:keyLabel="o" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="44" scummvm:keyLabel="p" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="29" scummvm:keyLabel="a" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="47" scummvm:keyLabel="s" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="32" scummvm:keyLabel="d" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="34" scummvm:keyLabel="f" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="35" scummvm:keyLabel="g" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="36" scummvm:keyLabel="h" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="38" scummvm:keyLabel="j" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="39" scummvm:keyLabel="k" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="40" scummvm:keyLabel="l" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="74" scummvm:keyLabel=";" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="-1" scummvm:keyLabel="⇪" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="54" scummvm:keyLabel="z" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="52" scummvm:keyLabel="x" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="31" scummvm:keyLabel="c" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="50" scummvm:keyLabel="v" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="30" scummvm:keyLabel="b" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="42" scummvm:keyLabel="n" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="41" scummvm:keyLabel="m" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="67" scummvm:keyLabel="⬅" scummvm:keyWidth="20%p" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow rowEdgeFlags="bottom">
+		<CustomKey scummvm:codes="-6" scummvm:keyLabel="123…" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="71" scummvm:keyLabel="[" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="72" scummvm:keyLabel="]" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="76" scummvm:keyLabel="/" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="62" scummvm:keyLabel="Space" scummvm:keyWidth="20%p" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="55" scummvm:keyLabel="," scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="56" scummvm:keyLabel="." scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="66" scummvm:keyLabel="Enter" scummvm:keyWidth="20%p" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+</CustomKeyboard>
diff --git a/dists/android/res/xml/qwerty_alt.xml b/dists/android/res/xml/qwerty_alt.xml
new file mode 100644
index 0000000000..b7dacc70f1
--- /dev/null
+++ b/dists/android/res/xml/qwerty_alt.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- When creating new keyboard layout, add it to TextInputKeyboardList array in ScummVMActivity.java -->
+<CustomKeyboard xmlns:scummvm="http://schemas.android.com/apk/res-auto"
+	scummvm:keyWidth="10%p"
+	scummvm:horizontalGap="0px"
+	scummvm:verticalGap="0px"
+	scummvm:keyHeight="10%p">
+	<CustomRow>
+		<CustomKey scummvm:codes="8"  scummvm:keyLabel="1" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="9"  scummvm:keyLabel="2" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="10" scummvm:keyLabel="3" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="11" scummvm:keyLabel="4" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="12" scummvm:keyLabel="5" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="13" scummvm:keyLabel="6" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="14" scummvm:keyLabel="7" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="15" scummvm:keyLabel="8" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="16" scummvm:keyLabel="9" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="7"  scummvm:keyLabel="0" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="111"  scummvm:keyLabel="Esc" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="68"  scummvm:keyLabel="`" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="69"  scummvm:keyLabel="-" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="70"  scummvm:keyLabel="=" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="73"  scummvm:keyLabel="\\" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="124" scummvm:keyLabel="Ins" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="92"  scummvm:keyLabel="PgUp" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="122" scummvm:keyLabel="Home" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="19"  scummvm:keyLabel="↑" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="123" scummvm:keyLabel="End" scummvm:isRepeatable="true" scummvm:keyEdgeFlags="right" />
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="-1"  scummvm:keyLabel="!@#…" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="75"  scummvm:keyLabel="'" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100075"  scummvm:keyLabel=""" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="61"  scummvm:keyLabel="Tab" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="115" scummvm:keyLabel="CapsLk" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="112" scummvm:keyLabel="Del" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="93"  scummvm:keyLabel="PgDn" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="21"  scummvm:keyLabel="←" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="20"  scummvm:keyLabel="↓" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="22"  scummvm:keyLabel="→" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow rowEdgeFlags="bottom">
+		<CustomKey scummvm:codes="-6"  scummvm:keyLabel="abc…"  scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="59"  scummvm:keyLabel="Shift" scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="113" scummvm:keyLabel="Ctrl"  scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="117" scummvm:keyLabel="Meta"     scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="57"  scummvm:keyLabel="Alt"   scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="58"  scummvm:keyLabel="Alt"   scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="118" scummvm:keyLabel="Meta"     scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="226" scummvm:keyLabel="Menu"   scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="114" scummvm:keyLabel="Ctrl"  scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="60"  scummvm:keyLabel="Shift" scummvm:isSticky="true"/>
+	</CustomRow>
+</CustomKeyboard>
diff --git a/dists/android/res/xml/qwerty_alt_shift.xml b/dists/android/res/xml/qwerty_alt_shift.xml
new file mode 100644
index 0000000000..eec092eeac
--- /dev/null
+++ b/dists/android/res/xml/qwerty_alt_shift.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- When creating new keyboard layout, add it to TextInputKeyboardList array in ScummVMActivity.java -->
+<CustomKeyboard xmlns:scummvm="http://schemas.android.com/apk/res-auto"
+	scummvm:keyWidth="10%p"
+	scummvm:horizontalGap="0px"
+	scummvm:verticalGap="0px"
+	scummvm:keyHeight="10%p">
+	<CustomRow>
+		<CustomKey scummvm:codes="100008" scummvm:keyLabel="!" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100009" scummvm:keyLabel="\@" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100010" scummvm:keyLabel="#" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100011" scummvm:keyLabel="$" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100012" scummvm:keyLabel="%" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100013" scummvm:keyLabel="^" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100014" scummvm:keyLabel="&" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100015" scummvm:keyLabel="*" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100016" scummvm:keyLabel="(" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100007" scummvm:keyLabel=")" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="111"  scummvm:keyLabel="Esc" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100068"  scummvm:keyLabel="~" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100069"  scummvm:keyLabel="_" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100070"  scummvm:keyLabel="+" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100073"  scummvm:keyLabel="|" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="100075"  scummvm:keyLabel=""" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="131" scummvm:keyLabel="F1" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="132" scummvm:keyLabel="F2" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="133" scummvm:keyLabel="F3" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="134" scummvm:keyLabel="F4" scummvm:isRepeatable="true" scummvm:keyEdgeFlags="right" />
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="-1"  scummvm:keyLabel="123…" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="143" scummvm:keyLabel="NumLk" scummvm:isSticky="true"/>
+		<CustomKey scummvm:codes="120" scummvm:keyLabel="Print" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="116" scummvm:keyLabel="ScrollLk" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="121" scummvm:keyLabel="Pause" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="158" scummvm:keyLabel="Kp ." scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="135" scummvm:keyLabel="F5" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="136" scummvm:keyLabel="F6" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="137" scummvm:keyLabel="F7" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="138" scummvm:keyLabel="F8" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow rowEdgeFlags="bottom">
+		<CustomKey scummvm:codes="-6"  scummvm:keyLabel="abc…" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="154" scummvm:keyLabel="Kp /" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="155" scummvm:keyLabel="Kp *" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="156" scummvm:keyLabel="Kp -" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="157" scummvm:keyLabel="Kp +" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="160" scummvm:keyLabel="Kp ↵" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="139" scummvm:keyLabel="F9"  scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="140" scummvm:keyLabel="F10" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="141" scummvm:keyLabel="F11" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="142" scummvm:keyLabel="F12" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+</CustomKeyboard>
diff --git a/dists/android/res/xml/qwerty_shift.xml b/dists/android/res/xml/qwerty_shift.xml
new file mode 100644
index 0000000000..5a740437b8
--- /dev/null
+++ b/dists/android/res/xml/qwerty_shift.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- When creating new keyboard layout, add it to TextInputKeyboardList array in ScummVMActivity.java -->
+<CustomKeyboard xmlns:scummvm="http://schemas.android.com/apk/res-auto"
+	scummvm:keyWidth="10%p"
+	scummvm:horizontalGap="0px"
+	scummvm:verticalGap="0px"
+	scummvm:keyHeight="10%p">
+	<CustomRow>
+		<CustomKey scummvm:codes="45" scummvm:keyLabel="Q" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="51" scummvm:keyLabel="W" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="33" scummvm:keyLabel="E" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="46" scummvm:keyLabel="R" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="48" scummvm:keyLabel="T" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="53" scummvm:keyLabel="Y" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="49" scummvm:keyLabel="U" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="37" scummvm:keyLabel="I" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="43" scummvm:keyLabel="O" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="44" scummvm:keyLabel="P" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="29" scummvm:keyLabel="A" scummvm:keyEdgeFlags="left" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="47" scummvm:keyLabel="S" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="32" scummvm:keyLabel="D" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="34" scummvm:keyLabel="F" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="35" scummvm:keyLabel="G" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="36" scummvm:keyLabel="H" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="38" scummvm:keyLabel="J" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="39" scummvm:keyLabel="K" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="40" scummvm:keyLabel="L" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="74" scummvm:keyLabel=":" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow>
+		<CustomKey scummvm:codes="-1" scummvm:keyLabel="⇫" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="54" scummvm:keyLabel="Z" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="52" scummvm:keyLabel="X" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="31" scummvm:keyLabel="C" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="50" scummvm:keyLabel="V" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="30" scummvm:keyLabel="B" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="42" scummvm:keyLabel="N" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="41" scummvm:keyLabel="M" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="67" scummvm:keyLabel="⬅" scummvm:keyWidth="20%p" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+	<CustomRow rowEdgeFlags="bottom">
+		<CustomKey scummvm:codes="-6" scummvm:keyLabel="!@#…" scummvm:keyEdgeFlags="left"/>
+		<CustomKey scummvm:codes="71" scummvm:keyLabel="{" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="72" scummvm:keyLabel="}" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="76" scummvm:keyLabel="\?" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="62" scummvm:keyLabel="Space" scummvm:keyWidth="20%p" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="55" scummvm:keyLabel="<" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="56" scummvm:keyLabel=">" scummvm:isRepeatable="true"/>
+		<CustomKey scummvm:codes="66" scummvm:keyLabel="Enter" scummvm:keyWidth="20%p" scummvm:keyEdgeFlags="right" scummvm:isRepeatable="true"/>
+	</CustomRow>
+</CustomKeyboard>




More information about the Scummvm-git-logs mailing list