1b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer/*
2b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * Copyright (C) 2016 The Android Open Source Project
3b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer *
4b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * Licensed under the Apache License, Version 2.0 (the "License");
5b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * you may not use this file except in compliance with the License.
6b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * You may obtain a copy of the License at
7b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer *
8b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer *      http://www.apache.org/licenses/LICENSE-2.0
9b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer *
10b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * Unless required by applicable law or agreed to in writing, software
11b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * distributed under the License is distributed on an "AS IS" BASIS,
12b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * See the License for the specific language governing permissions and
14b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * limitations under the License.
15b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer */
16b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
17b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerpackage com.android.inputmethod.latin.car;
18b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
19b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.animation.Animator;
20b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.animation.ValueAnimator;
21b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.annotation.SuppressLint;
22b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.content.Context;
23b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.content.res.TypedArray;
24b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Bitmap;
25b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Canvas;
26b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Color;
27b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Paint;
28b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Paint.Align;
29b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.PorterDuff;
30b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Rect;
31b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Region.Op;
32b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.Typeface;
33b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.graphics.drawable.Drawable;
34b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.inputmethodservice.Keyboard;
35b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.inputmethodservice.Keyboard.Key;
36b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.os.Handler;
37b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.os.Message;
38b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.util.AttributeSet;
39b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.util.Log;
40b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.util.TypedValue;
41b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.GestureDetector;
42b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.Gravity;
43b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.LayoutInflater;
44b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.MotionEvent;
45b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.View;
46b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.ViewConfiguration;
47b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.ViewGroup.LayoutParams;
48b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.accessibility.AccessibilityManager;
49b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.animation.AccelerateDecelerateInterpolator;
50b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.view.animation.LinearInterpolator;
51b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.widget.PopupWindow;
52b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport android.widget.TextView;
53b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
54b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport com.android.inputmethod.latin.R;
55b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
56b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.lang.ref.WeakReference;
57b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.Arrays;
58b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.HashMap;
59b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.List;
60b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.Locale;
61b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.Map;
62b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerimport java.util.regex.Pattern;
63b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
64b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer/**
65b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * A view that renders a virtual {@link android.inputmethodservice.Keyboard}. It handles rendering of keys and
66b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * detecting key presses and touch movements.
67b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer *
68b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_keyBackground
69b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
70b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
71b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_labelTextSize
72b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_keyTextSize
73b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_keyTextColor
74b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_verticalCorrection
75b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer * @attr ref android.R.styleable#KeyboardView_popupLayout
76b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer */
77b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyerpublic class KeyboardView extends View implements View.OnClickListener {
78b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
79b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
80b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Listener for virtual keyboard events.
81b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
82b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public interface OnKeyboardActionListener {
83b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
84b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
85b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user presses a key. This is sent before the {@link #onKey} is called.
86b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * For keys that repeat, this is only called once.
87b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
88b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * key, the value will be zero.
89b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
90b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void onPress(int primaryCode);
91b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
92b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
93b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user releases a key. This is sent after the {@link #onKey} is called.
94b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * For keys that repeat, this is only called once.
95b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * @param primaryCode the code of the key that was released
96b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
97b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void onRelease(int primaryCode);
98b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
99b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
100b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Send a key press to the listener.
101b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * @param primaryCode this is the key that was pressed
102b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * @param keyCodes the codes for all the possible alternative keys
103b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * with the primary code being the first. If the primary key code is
104b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * a single character such as an alphabet or number or symbol, the alternatives
105b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * will include other characters that may be on the same key or adjacent keys.
106b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * These codes are useful to correct for accidental presses of a key adjacent to
107b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * the intended key.
108b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
109b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void onKey(int primaryCode, int[] keyCodes);
110b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
111b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
112b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Sends a sequence of characters to the listener.
113b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * @param text the sequence of characters to be displayed.
114b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
115b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void onText(CharSequence text);
116b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
117b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
118b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user quickly moves the finger from right to left.
119b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
120b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void swipeLeft();
121b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
122b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
123b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user quickly moves the finger from left to right.
124b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
125b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void swipeRight();
126b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
127b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
128b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user quickly moves the finger from up to down.
129b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
130b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void swipeDown();
131b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
132b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
133b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when the user quickly moves the finger from down to up.
134b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
135b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void swipeUp();
136b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
137b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /**
138b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         * Called when we want to stop keyboard input.
139b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer         */
140b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        void stopInput();
141b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
142b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
143b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final boolean DEBUG = false;
144b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int NOT_A_KEY = -1;
145b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
146b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int SCRIM_ALPHA = 242;  // 95% opacity.
147b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MAX_ALPHA = 255;
148b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final float MIN_ANIMATION_VALUE = 0.0f;
149b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final float MAX_ANIMATION_VALUE = 1.0f;
150b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("_|-|,|\\.");
151b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    //private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
152b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Context mContext;
153b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
154b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Keyboard mKeyboard;
155b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mCurrentKeyIndex = NOT_A_KEY;
156b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLabelTextSize;
157b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mKeyTextSize;
158b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mKeyPunctuationSize;
159b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mKeyTextColorPrimary;
160b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mKeyTextColorSecondary;
161b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private String mFontFamily;
162b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mTextStyle = Typeface.BOLD;
163b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
164b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private TextView mPreviewText;
165b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final PopupWindow mPreviewPopup;
166b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPreviewTextSizeLarge;
167b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPreviewOffset;
168b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPreviewHeight;
169b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    // Working variable
170b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final int[] mCoordinates = new int[2];
171b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
172b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private KeyboardView mPopupKeyboardView;
173b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mPopupKeyboardOnScreen;
174b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private View mPopupParent;
175b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mMiniKeyboardOffsetX;
176b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mMiniKeyboardOffsetY;
177b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Map<Key,View> mMiniKeyboardCache;
178b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Key[] mKeys;
179b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
180b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** Listener for {@link OnKeyboardActionListener}. */
181b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private OnKeyboardActionListener mKeyboardActionListener;
182b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
183b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MSG_SHOW_PREVIEW = 1;
184b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MSG_REMOVE_PREVIEW = 2;
185b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MSG_REPEAT = 3;
186b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MSG_LONGPRESS = 4;
187b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
188b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int DELAY_BEFORE_PREVIEW = 0;
189b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int DELAY_AFTER_PREVIEW = 70;
190b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int DEBOUNCE_TIME = 70;
191b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
192b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int ANIMATE_IN_OUT_DURATION_MS = 300;
193b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int ANIMATE_SCRIM_DURATION_MS = 150;
194b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int ANIMATE_SCRIM_DELAY_MS = 225;
195b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
196b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mVerticalCorrection;
197b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mProximityThreshold;
198b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
199b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final boolean mPreviewCentered = false;
200b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mShowPreview = true;
201b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final boolean mShowTouchPoints = true;
202b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPopupPreviewX;
203b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPopupPreviewY;
204b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mPopupScrimColor;
205b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mBackgroundColor;
206b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
207b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastX;
208b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastY;
209b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mStartX;
210b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mStartY;
211b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
212b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mProximityCorrectOn;
213b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
214b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Paint mPaint;
215b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Rect mPadding;
216b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
217b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private long mDownTime;
218b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private long mLastMoveTime;
219b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastKey;
220b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastCodeX;
221b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastCodeY;
222b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mCurrentKey = NOT_A_KEY;
223b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mDownKey = NOT_A_KEY;
224b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private long mLastKeyTime;
225b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private long mCurrentKeyTime;
226b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final int[] mKeyIndices = new int[12];
227b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private GestureDetector mGestureDetector;
228b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mRepeatKeyIndex = NOT_A_KEY;
229b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mAbortKey;
230b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Key mInvalidatedKey;
231b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Rect mClipRegion = new Rect(0, 0, 0, 0);
232b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mPossiblePoly;
233b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final SwipeTracker mSwipeTracker = new SwipeTracker();
234b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final int mSwipeThreshold;
235b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final boolean mDisambiguateSwipe;
236b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
237b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    // Variables for dealing with multiple pointers
238b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mOldPointerCount = 1;
239b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private float mOldPointerX;
240b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private float mOldPointerY;
241b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
242b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Drawable mKeyBackground;
243b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
244b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
245b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int REPEAT_START_DELAY = 400;
246b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
247b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
248b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static int MAX_NEARBY_KEYS = 12;
249b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
250b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
251b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    // For multi-tap
252b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mLastSentIndex;
253b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mTapCount;
254b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private long mLastTapTime;
255b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mInMultiTap;
256b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static final int MULTITAP_INTERVAL = 800; // milliseconds
257b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final StringBuilder mPreviewLabel = new StringBuilder(1);
258b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
259b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
260b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mDrawPending;
261b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** The dirty region in the keyboard bitmap */
262b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private final Rect mDirtyRect = new Rect();
263b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** The keyboard bitmap for faster updates */
264b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Bitmap mBuffer;
265b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
266b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mKeyboardChanged;
267b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** The canvas for the above mutable keyboard bitmap */
268b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Canvas mCanvas;
269b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /** The accessibility manager for accessibility support */
270b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private AccessibilityManager mAccessibilityManager;
271b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
272b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean mUseSecondaryColor = true;
273b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private Locale mLocale;
274b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
275b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    Handler mHandler = new KeyboardHander(this);
276b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
277b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private float mAnimateInValue;
278b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private ValueAnimator mAnimateInAnimator;
279b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private float mAnimateOutValue;
280b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private ValueAnimator mAnimateOutAnimator;
281b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mScrimAlpha;
282b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private ValueAnimator mScrimAlphaAnimator;
283b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int mAnimateCenter;
284b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
285b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public KeyboardView(Context context, AttributeSet attrs) {
286b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        this(context, attrs, 0);
287b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
288b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
289b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
290b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        super(context, attrs, defStyle);
291b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mContext = context;
292b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
293b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        TypedArray a =
294b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                context.obtainStyledAttributes(
295b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        attrs, R.styleable.KeyboardView, defStyle, 0);
296b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
297b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        LayoutInflater inflate =
298b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                (LayoutInflater) context
299b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
300b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
301b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int previewLayout = 0;
302b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
303b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int n = a.getIndexCount();
304b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
305b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        for (int i = 0; i < n; i++) {
306b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int attr = a.getIndex(i);
307b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
308b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            switch (attr) {
309b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_keyBackground:
310b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mKeyBackground = a.getDrawable(attr);
311b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
312b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer/*                case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
313b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
314b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
315b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
316b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    previewLayout = a.getResourceId(attr, 0);
317b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
318b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
319b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
320b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
321b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
322b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mPreviewHeight = a.getDimensionPixelSize(attr, 80);
323b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break; */
324b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_keyTextSize:
325b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mKeyTextSize = a.getDimensionPixelSize(attr, 18);
326b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
327b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_keyTextColorPrimary:
328b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mKeyTextColorPrimary = a.getColor(attr, 0xFF000000);
329b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
330b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_keyTextColorSecondary:
331b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mKeyTextColorSecondary = a.getColor(attr, 0x99000000);
332b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
333b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_labelTextSize:
334b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mLabelTextSize = a.getDimensionPixelSize(attr, 14);
335b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
336b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_fontFamily:
337b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mFontFamily = a.getString(attr);
338b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
339b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case R.styleable.KeyboardView_textStyle:
340b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mTextStyle = a.getInt(attr, 0);
341b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
342b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
343b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
344b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
345b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        a.recycle();
346b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        a = mContext.obtainStyledAttributes(R.styleable.KeyboardView);
347b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        a.recycle();
348b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
349b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPreviewPopup = new PopupWindow(context);
350b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (previewLayout != 0) {
351b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
352b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
353b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewPopup.setContentView(mPreviewText);
354b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewPopup.setBackgroundDrawable(null);
355b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
356b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mShowPreview = false;
357b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
358b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
359b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPreviewPopup.setTouchable(false);
360b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
361b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupParent = this;
362b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        //mPredicting = true;
363b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
364b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPaint = new Paint();
365b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPaint.setAntiAlias(true);
366b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPaint.setTextSize(mKeyTextSize);
367b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPaint.setTextAlign(Align.CENTER);
368b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPaint.setAlpha(MAX_ALPHA);
369b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
370b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mFontFamily != null) {
371b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            Typeface typeface = Typeface.create(mFontFamily, mTextStyle);
372b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPaint.setTypeface(typeface);
373b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
374b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        else {
375b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, mTextStyle));
376b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
377b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
378b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPadding = new Rect(0, 0, 0, 0);
379b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mMiniKeyboardCache = new HashMap<Key,View>();
380b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyBackground.getPadding(mPadding);
381b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
382b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
383b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDisambiguateSwipe = true;
384b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
385d37a2ccc556bf38860c18558db470516f1a146e5Anthony Chen        int color = getResources().getColor(R.color.car_dark_blue_grey_700);
386b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupScrimColor = Color.argb(
387b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                SCRIM_ALPHA, Color.red(color), Color.green(color), Color.blue(color));
388b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mBackgroundColor = Color.TRANSPARENT;
389b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
390b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyPunctuationSize =
391b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                (int) getResources().getDimension(R.dimen.keyboard_key_punctuation_height);
392b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
393b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        resetMultiTap();
394b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        initGestureDetector();
395b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
396b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateInValue = MAX_ANIMATION_VALUE;
397b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateInAnimator = ValueAnimator.ofFloat(MIN_ANIMATION_VALUE, MAX_ANIMATION_VALUE);
398b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        AccelerateDecelerateInterpolator accInterpolator = new AccelerateDecelerateInterpolator();
399b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateInAnimator.setInterpolator(accInterpolator);
400b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateInAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
401b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
402b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationUpdate(ValueAnimator valueAnimator) {
403b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mAnimateInValue = (float) valueAnimator.getAnimatedValue();
404b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateAllKeys();
405b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
406b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        });
407b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateInAnimator.setDuration(ANIMATE_IN_OUT_DURATION_MS);
408b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
409b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mScrimAlpha = 0;
410b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mScrimAlphaAnimator = ValueAnimator.ofInt(0, SCRIM_ALPHA);
411b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        LinearInterpolator linearInterpolator = new LinearInterpolator();
412b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mScrimAlphaAnimator.setInterpolator(linearInterpolator);
413b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mScrimAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
414b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
415b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationUpdate(ValueAnimator valueAnimator) {
416b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mScrimAlpha = (int) valueAnimator.getAnimatedValue();
417b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateAllKeys();
418b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
419b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        });
420b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mScrimAlphaAnimator.setDuration(ANIMATE_SCRIM_DURATION_MS);
421b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
422b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutValue = MIN_ANIMATION_VALUE;
423b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutAnimator = ValueAnimator.ofFloat(MIN_ANIMATION_VALUE, MAX_ANIMATION_VALUE);
424b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutAnimator.setInterpolator(accInterpolator);
425b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
426b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
427b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationUpdate(ValueAnimator valueAnimator) {
428b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mAnimateOutValue = (float) valueAnimator.getAnimatedValue();
429b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateAllKeys();
430b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
431b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        });
432b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutAnimator.setDuration(ANIMATE_IN_OUT_DURATION_MS);
433b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAnimateOutAnimator.addListener(new Animator.AnimatorListener() {
434b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
435b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationEnd(Animator animation) {
436b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                setVisibility(View.GONE);
437b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mAnimateOutValue = MIN_ANIMATION_VALUE;
438b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
439b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
440b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
441b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationCancel(Animator animator) {
442b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
443b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
444b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
445b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationRepeat(Animator animator) {
446b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
447b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
448b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
449b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public void onAnimationStart(Animator animator) {
450b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
451b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        });
452b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
453b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
454b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
455b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void initGestureDetector() {
456b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
457b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            @Override
458b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            public boolean onFling(MotionEvent me1, MotionEvent me2,
459b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    float velocityX, float velocityY) {
460b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mPossiblePoly) return false;
461b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final float absX = Math.abs(velocityX);
462b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final float absY = Math.abs(velocityY);
463b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                float deltaX = me2.getX() - me1.getX();
464b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                float deltaY = me2.getY() - me1.getY();
465b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                int travelX = getWidth() / 2; // Half the keyboard width
466b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                int travelY = getHeight() / 2; // Half the keyboard height
467b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mSwipeTracker.computeCurrentVelocity(1000);
468b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final float endingVelocityX = mSwipeTracker.getXVelocity();
469b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final float endingVelocityY = mSwipeTracker.getYVelocity();
470b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                boolean sendDownKey = false;
471b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
472b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
473b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        sendDownKey = true;
474b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
475b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        swipeRight();
476b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        return true;
477b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
478b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
479b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
480b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        sendDownKey = true;
481b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
482b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        swipeLeft();
483b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        return true;
484b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
485b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
486b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
487b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        sendDownKey = true;
488b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
489b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        swipeUp();
490b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        return true;
491b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
492b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
493b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
494b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        sendDownKey = true;
495b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
496b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        swipeDown();
497b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        return true;
498b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
499b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
500b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
501b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (sendDownKey) {
502b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
503b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
504b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                return false;
505b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
506b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        });
507b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
508b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mGestureDetector.setIsLongpressEnabled(false);
509b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
510b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
511b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
512b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardActionListener = listener;
513b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
514b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
515b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
516b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Returns the {@link OnKeyboardActionListener} object.
517b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return the listener attached to this keyboard
518b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
519b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected OnKeyboardActionListener getOnKeyboardActionListener() {
520b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return mKeyboardActionListener;
521b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
522b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
523b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
524b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
525b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * view will re-layout itself to accommodate the keyboard.
526b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see android.inputmethodservice.Keyboard
527b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #getKeyboard()
528b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param keyboard the keyboard to display in this view
529b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
530b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setKeyboard(Keyboard keyboard, Locale locale) {
531b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard != null) {
532b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            showPreview(NOT_A_KEY);
533b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
534b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Remove any pending messages
535b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        removeMessages();
536b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboard = keyboard;
537b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        List<Key> keys = mKeyboard.getKeys();
538b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeys = keys.toArray(new Key[keys.size()]);
539b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        requestLayout();
540b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Hint to reallocate the buffer if the size changed
541b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardChanged = true;
542b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        invalidateAllKeys();
543b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        computeProximityThreshold(keyboard);
544b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
545b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Switching to a different keyboard should abort any pending keys so that the key up
546b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // doesn't get delivered to the old or new keyboard
547b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mAbortKey = true; // Until the next ACTION_DOWN
548b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mLocale = locale;
549b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
550b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
551b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
552b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Returns the current keyboard being displayed by this view.
553b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return the currently attached keyboard
554b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #setKeyboard(android.inputmethodservice.Keyboard, java.util.Locale)
555b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
556b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public Keyboard getKeyboard() {
557b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return mKeyboard;
558b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
559b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
560b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
561b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Sets the state of the shift key of the keyboard, if any.
562b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param shifted whether or not to enable the state of the shift key
563b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return true if the shift key state changed, false if there was no change
564b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see KeyboardView#isShifted()
565b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
566b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean setShifted(boolean shifted) {
567b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard != null) {
568b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (mKeyboard.setShifted(shifted)) {
569b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // The whole keyboard probably needs to be redrawn
570b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateAllKeys();
571b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                return true;
572b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
573b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
574b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return false;
575b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
576b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
577b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
578b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Returns the state of the shift key of the keyboard, if any.
579b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return true if the shift is in a pressed state, false otherwise. If there is
580b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * no shift key on the keyboard or there is no keyboard attached, it returns false.
581b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see KeyboardView#setShifted(boolean)
582b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
583b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean isShifted() {
584b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard != null) {
585b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return mKeyboard.isShifted();
586b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
587b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return false;
588b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
589b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
590b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
591b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Enables or disables the key feedback popup. This is a popup that shows a magnified
592b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * version of the depressed key. By default the preview is enabled.
593b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param previewEnabled whether or not to enable the key feedback popup
594b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #isPreviewEnabled()
595b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
596b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setPreviewEnabled(boolean previewEnabled) {
597b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mShowPreview = previewEnabled;
598b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
599b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
600b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
601b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Returns the enabled state of the key feedback popup.
602b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return whether or not the key feedback popup is enabled
603b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #setPreviewEnabled(boolean)
604b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
605b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean isPreviewEnabled() {
606b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return mShowPreview;
607b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
608b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
609b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setPopupParent(View v) {
610b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupParent = v;
611b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
612b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
613b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setPopupOffset(int x, int y) {
614b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mMiniKeyboardOffsetX = x;
615b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mMiniKeyboardOffsetY = y;
616b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPreviewPopup.isShowing()) {
617b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewPopup.dismiss();
618b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
619b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
620b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
621b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
622b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
623b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * codes for adjacent keys.  When disabled, only the primary key code will be
624b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * reported.
625b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param enabled whether or not the proximity correction is enabled
626b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
627b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setProximityCorrectionEnabled(boolean enabled) {
628b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mProximityCorrectOn = enabled;
629b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
630b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
631b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
632b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Returns true if proximity correction is enabled.
633b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
634b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean isProximityCorrectionEnabled() {
635b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return mProximityCorrectOn;
636b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
637b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
638b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean getUseSecondaryColor() {
639b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return mUseSecondaryColor;
640b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
641b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
642b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setUseSecondaryColor(boolean useSecondaryColor) {
643b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mUseSecondaryColor = useSecondaryColor;
644b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
645b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
646b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
647b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Popup keyboard close button clicked.
648b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @hide
649b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
650b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
651b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void onClick(View v) {
652b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        dismissPopupKeyboard();
653b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
654b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
655b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private CharSequence adjustCase(CharSequence label) {
656b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard.isShifted() && label != null && label.length() < 3
657b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                && Character.isLowerCase(label.charAt(0))) {
658b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            label = label.toString().toUpperCase(mLocale);
659b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
660b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return label;
661b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
662b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
663b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
664b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
665b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Round up a little
666b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard == null) {
667b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
668b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
669b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
670b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
671b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                width = MeasureSpec.getSize(widthMeasureSpec);
672b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
673b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
674b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
675b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
676b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
677b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
678b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Compute the average distance between adjacent keys (horizontally and vertically)
679b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * and square it to get the proximity threshold. We use a square here and in computing
680b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * the touch distance from a key's center to avoid taking a square root.
681b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param keyboard
682b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
683b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void computeProximityThreshold(Keyboard keyboard) {
684b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (keyboard == null) return;
685b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key[] keys = mKeys;
686b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (keys == null) return;
687b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int length = keys.length;
688b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int dimensionSum = 0;
689b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        for (int i = 0; i < length; i++) {
690b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            Key key = keys[i];
691b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            dimensionSum += Math.min(key.width, key.height) + key.gap;
692b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
693b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (dimensionSum < 0 || length == 0) return;
694b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mProximityThreshold = (int) (dimensionSum * 1.4f / length);
695b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mProximityThreshold *= mProximityThreshold; // Square it
696b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
697b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
698b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
699b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void onSizeChanged(int w, int h, int oldw, int oldh) {
700b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        super.onSizeChanged(w, h, oldw, oldh);
701b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard != null) {
702b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            //TODO(ftamp): mKeyboard.resize(w, h);
703b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
704b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Release the buffer, if any and it will be reallocated on the next draw
705b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mBuffer = null;
706b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
707b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
708b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
709b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void onDraw(Canvas canvas) {
710b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        super.onDraw(canvas);
711b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mDrawPending || mBuffer == null || mKeyboardChanged) {
712b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            onBufferDraw();
713b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
714b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.drawBitmap(mBuffer, 0, 0, null);
715b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
716b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
717b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @SuppressWarnings("unused")
718b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void onBufferDraw() {
719b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mBuffer == null || mKeyboardChanged) {
720b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (mBuffer == null || mKeyboardChanged &&
721b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
722b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Make sure our bitmap is at least 1x1
723b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int width = Math.max(1, getWidth());
724b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int height = Math.max(1, getHeight());
725b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
726b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mCanvas = new Canvas(mBuffer);
727b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
728b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            invalidateAllKeys();
729b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mKeyboardChanged = false;
730b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
731b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Canvas canvas = mCanvas;
732b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.clipRect(mDirtyRect, Op.REPLACE);
733b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
734b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeyboard == null) return;
735b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
736b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Paint paint = mPaint;
737b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Drawable keyBackground = mKeyBackground;
738b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Rect clipRegion = mClipRegion;
739b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Rect padding = mPadding;
740b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int kbdPaddingLeft = getPaddingLeft();
741b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int kbdPaddingTop = getPaddingTop();
742b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key[] keys = mKeys;
743b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key invalidKey = mInvalidatedKey;
744b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
745b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        paint.setColor(mKeyTextColorPrimary);
746b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        boolean drawSingleKey = false;
747b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
748b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // Is clipRegion completely contained within the invalidated key?
749b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
750b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
751b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
752b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
753b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                drawSingleKey = true;
754b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
755b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
756b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
757b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
758b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Animate in translation. No overall translation for animating out.
759b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        float offset = getWidth()/2 * (1 - mAnimateInValue);
760b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.translate(offset, 0);
761b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
762b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Draw background.
763b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        float backgroundWidth = getWidth() * (mAnimateInValue - mAnimateOutValue);
764b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        paint.setColor(mBackgroundColor);
765b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int center;
766b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mLastSentIndex != NOT_A_KEY && mLastSentIndex < keys.length) {
767b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            center = keys[mLastSentIndex].x + keys[mLastSentIndex].width / 2;
768b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
769b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            center = getWidth()/2;
770b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
771b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.drawRect(mAnimateOutValue * center, 0, mAnimateOutValue * center + backgroundWidth,
772b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                getHeight(), paint);
773b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int keyCount = keys.length;
774b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        for (int i = 0; i < keyCount; i++) {
775b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final Key key = keys[i];
776b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (drawSingleKey && invalidKey != key) {
777b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                continue;
778b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
779b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int[] drawableState = key.getCurrentDrawableState();
780b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            keyBackground.setState(drawableState);
781b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (key.icon != null) {
782b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                key.icon.setState(drawableState);
783b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
784b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
785b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
786b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // Switch the character to uppercase if shift is pressed
787b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            String label = key.label == null? null : adjustCase(key.label).toString();
788b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
789b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final Rect bounds = keyBackground.getBounds();
790b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (key.width != bounds.right ||
791b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    key.height != bounds.bottom) {
792b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                keyBackground.setBounds(0, 0, key.width, key.height);
793b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
794b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            float animateOutOffset = 0;
795b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (mAnimateOutValue != MIN_ANIMATION_VALUE) {
796b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                animateOutOffset = (center - key.x - key.width/2) * mAnimateOutValue;
797b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
798b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.translate(key.x + kbdPaddingLeft + animateOutOffset, key.y + kbdPaddingTop);
799b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            keyBackground.draw(canvas);
800b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
801b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (label != null && label.length() > 0) {
802b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Use primary color for letters and digits, secondary color for everything else
803b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (Character.isLetterOrDigit(label.charAt(0))) {
804b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setColor(mKeyTextColorPrimary);
805b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (mUseSecondaryColor) {
806b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setColor(mKeyTextColorSecondary);
807b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
808b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // For characters, use large font. For labels like "Done", use small font.
809b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (label.length() > 1 && key.codes.length < 2) {
810b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setTextSize(mLabelTextSize);
811b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (isPunctuation(label)) {
812b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setTextSize(mKeyPunctuationSize);
813b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else {
814b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setTextSize(mKeyTextSize);
815b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
816b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mAnimateInValue != MAX_ANIMATION_VALUE) {
817b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    int alpha =
818b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            (int) ((backgroundWidth - key.x - key.width) / key.width * MAX_ALPHA);
819b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (alpha > MAX_ALPHA) {
820b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = MAX_ALPHA;
821b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else if (alpha < 0) {
822b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = 0;
823b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
824b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setAlpha(alpha);
825b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (mAnimateOutValue != MIN_ANIMATION_VALUE) {
826b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    int alpha;
827b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (i == mLastSentIndex) {
828b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        // Fade out selected character from 1/2 to 3/4 of animate out.
829b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = (int) ((3 - mAnimateOutValue * 4) * MAX_ALPHA);
830b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
831b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        // Fade out the rest from start to 1/2 of animate out.
832b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = (int) ((1 - mAnimateOutValue * 2) * MAX_ALPHA);
833b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
834b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (alpha > MAX_ALPHA) {
835b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = MAX_ALPHA;
836b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else if (alpha < 0) {
837b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        alpha = 0;
838b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
839b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    paint.setAlpha(alpha);
840b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
841b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Draw the text
842b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                canvas.drawText(label,
843b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        (key.width - padding.left - padding.right) / 2
844b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                + padding.left,
845b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        (key.height - padding.top - padding.bottom) / 2
846b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
847b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        paint);
848b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Turn off drop shadow
849b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                paint.setShadowLayer(0, 0, 0, 0);
850b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else if (key.icon != null) {
851b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int drawableX = (key.width - padding.left - padding.right
852b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        - key.icon.getIntrinsicWidth()) / 2 + padding.left;
853b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int drawableY = (key.height - padding.top - padding.bottom
854b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        - key.icon.getIntrinsicHeight()) / 2 + padding.top;
855b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                canvas.translate(drawableX, drawableY);
856b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                key.icon.setBounds(0, 0,
857b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
858b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                key.icon.draw(canvas);
859b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                canvas.translate(-drawableX, -drawableY);
860b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
861b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.translate(-key.x - kbdPaddingLeft - animateOutOffset, -key.y - kbdPaddingTop);
862b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
863b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mInvalidatedKey = null;
864b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Overlay a dark rectangle to dim the keyboard
865b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        paint.setColor(mPopupScrimColor);
866b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        paint.setAlpha(mScrimAlpha);
867b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
868b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        canvas.translate(-offset, 0);
869b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
870b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (DEBUG && mShowTouchPoints) {
871b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            paint.setAlpha(128);
872b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            paint.setColor(0xFFFF0000);
873b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.drawCircle(mStartX, mStartY, 3, paint);
874b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
875b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            paint.setColor(0xFF0000FF);
876b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.drawCircle(mLastX, mLastY, 3, paint);
877b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            paint.setColor(0xFF00FF00);
878b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
879b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
880b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
881b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDrawPending = false;
882b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDirtyRect.setEmpty();
883b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
884b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
885b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean isPunctuation(String label) {
886b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return PUNCTUATION_PATTERN.matcher(label).matches();
887b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
888b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
889b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private int getKeyIndices(int x, int y, int[] allKeys) {
890b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key[] keys = mKeys;
891b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int primaryIndex = NOT_A_KEY;
892b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int closestKey = NOT_A_KEY;
893b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int closestKeyDist = mProximityThreshold + 1;
894b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        Arrays.fill(mDistances, Integer.MAX_VALUE);
895b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
896b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int keyCount = nearestKeyIndices.length;
897b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        for (int i = 0; i < keyCount; i++) {
898b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final Key key = keys[nearestKeyIndices[i]];
899b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int dist = 0;
900b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            boolean isInside = key.isInside(x,y);
901b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (isInside) {
902b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                primaryIndex = nearestKeyIndices[i];
903b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
904b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
905b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (((mProximityCorrectOn
906b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
907b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    || isInside)
908b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    && key.codes[0] > 32) {
909b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Find insertion point
910b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int nCodes = key.codes.length;
911b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (dist < closestKeyDist) {
912b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    closestKeyDist = dist;
913b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    closestKey = nearestKeyIndices[i];
914b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
915b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
916b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (allKeys == null) continue;
917b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
918b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                for (int j = 0; j < mDistances.length; j++) {
919b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mDistances[j] > dist) {
920b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        // Make space for nCodes codes
921b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        System.arraycopy(mDistances, j, mDistances, j + nCodes,
922b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                mDistances.length - j - nCodes);
923b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        System.arraycopy(allKeys, j, allKeys, j + nCodes,
924b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                allKeys.length - j - nCodes);
925b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        for (int c = 0; c < nCodes; c++) {
926b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            allKeys[j + c] = key.codes[c];
927b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mDistances[j + c] = dist;
928b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        }
929b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
930b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
931b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
932b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
933b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
934b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (primaryIndex == NOT_A_KEY) {
935b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            primaryIndex = closestKey;
936b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
937b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return primaryIndex;
938b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
939b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
940b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void detectAndSendKey(int index, int x, int y, long eventTime) {
941b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (index != NOT_A_KEY && index < mKeys.length) {
942b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final Key key = mKeys[index];
943b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (key.text != null) {
944b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mKeyboardActionListener.onText(key.text);
945b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mKeyboardActionListener.onRelease(NOT_A_KEY);
946b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
947b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                int code = key.codes[0];
948b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                //TextEntryState.keyPressedAt(key, x, y);
949b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                int[] codes = new int[MAX_NEARBY_KEYS];
950b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                Arrays.fill(codes, NOT_A_KEY);
951b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                getKeyIndices(x, y, codes);
952b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Multi-tap
953b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mInMultiTap) {
954b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mTapCount != -1) {
955b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
956b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
957b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mTapCount = 0;
958b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
959b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    code = key.codes[mTapCount];
960b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
961b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mKeyboardActionListener.onKey(code, codes);
962b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mKeyboardActionListener.onRelease(code);
963b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
964b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mLastSentIndex = index;
965b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mLastTapTime = eventTime;
966b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
967b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
968b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
969b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
970b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Handle multi-tap keys by producing the key label for the current multi-tap state.
971b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
972b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private CharSequence getPreviewText(Key key) {
973b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mInMultiTap) {
974b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // Multi-tap
975b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewLabel.setLength(0);
976b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
977b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return adjustCase(mPreviewLabel);
978b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
979b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return adjustCase(key.label);
980b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
981b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
982b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
983b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void showPreview(int keyIndex) {
984b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int oldKeyIndex = mCurrentKeyIndex;
985b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final PopupWindow previewPopup = mPreviewPopup;
986b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
987b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mCurrentKeyIndex = keyIndex;
988b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Release the old key and press the new key
989b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key[] keys = mKeys;
990b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (oldKeyIndex != mCurrentKeyIndex) {
991b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
992b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                Key oldKey = keys[oldKeyIndex];
993b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Keyboard.Key.onReleased(boolean) should be called here, but due to b/21446448,
994b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // onReleased doesn't check the input param and it always toggles the on state.
995b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Therefore we need to just set the pressed state to false here.
996b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                oldKey.pressed = false;
997b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateKey(oldKeyIndex);
998b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
999b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
1000b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                Key newKey = keys[mCurrentKeyIndex];
1001b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                newKey.onPressed();
1002b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateKey(mCurrentKeyIndex);
1003b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1004b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1005b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // If key changed and preview is on ...
1006b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
1007b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mHandler.removeMessages(MSG_SHOW_PREVIEW);
1008b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (previewPopup.isShowing()) {
1009b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (keyIndex == NOT_A_KEY) {
1010b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mHandler.sendMessageDelayed(mHandler
1011b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                    .obtainMessage(MSG_REMOVE_PREVIEW),
1012b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            DELAY_AFTER_PREVIEW);
1013b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1014b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1015b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (keyIndex != NOT_A_KEY) {
1016b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
1017b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    // Show right away, if it's already visible and finger is moving around
1018b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    showKey(keyIndex);
1019b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else {
1020b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mHandler.sendMessageDelayed(
1021b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
1022b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            DELAY_BEFORE_PREVIEW);
1023b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1024b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1025b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1026b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1027b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1028b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void showKey(final int keyIndex) {
1029b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final PopupWindow previewPopup = mPreviewPopup;
1030b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key[] keys = mKeys;
1031b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (keyIndex < 0 || keyIndex >= mKeys.length) return;
1032b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        Key key = keys[keyIndex];
1033b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (key.icon != null) {
1034b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewText.setCompoundDrawables(null, null, null,
1035b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    key.iconPreview != null ? key.iconPreview : key.icon);
1036b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewText.setText(null);
1037b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
1038b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewText.setCompoundDrawables(null, null, null, null);
1039b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewText.setText(getPreviewText(key));
1040b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (key.label.length() > 1 && key.codes.length < 2) {
1041b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
1042b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
1043b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1044b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
1045b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPreviewText.setTypeface(Typeface.DEFAULT);
1046b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1047b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1048b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1049b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
1050b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
1051b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
1052b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int popupHeight = mPreviewHeight;
1053b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        LayoutParams lp = mPreviewText.getLayoutParams();
1054b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (lp != null) {
1055b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            lp.width = popupWidth;
1056b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            lp.height = popupHeight;
1057b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1058b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (!mPreviewCentered) {
1059b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
1060b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
1061b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
1062b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // TODO: Fix this if centering is brought back
1063b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
1064b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupPreviewY = - mPreviewText.getMeasuredHeight();
1065b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1066b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mHandler.removeMessages(MSG_REMOVE_PREVIEW);
1067b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        getLocationInWindow(mCoordinates);
1068b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
1069b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
1070b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1071b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Set the preview background state
1072b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        /*
1073b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPreviewText.getBackground().setState(
1074b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
1075b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        */
1076b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupPreviewX += mCoordinates[0];
1077b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupPreviewY += mCoordinates[1];
1078b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1079b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // If the popup cannot be shown above the key, put it on the side
1080b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        getLocationOnScreen(mCoordinates);
1081b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPopupPreviewY + mCoordinates[1] < 0) {
1082b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // If the key you're pressing is on the left side of the keyboard, show the popup on
1083b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            // the right, offset by enough to see at least one key to the left/right.
1084b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (key.x + key.width <= getWidth() / 2) {
1085b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPopupPreviewX += (int) (key.width * 2.5);
1086b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1087b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPopupPreviewX -= (int) (key.width * 2.5);
1088b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1089b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupPreviewY += popupHeight;
1090b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1091b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1092b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (previewPopup.isShowing()) {
1093b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            previewPopup.update(mPopupPreviewX, mPopupPreviewY,
1094b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    popupWidth, popupHeight);
1095b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
1096b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            previewPopup.setWidth(popupWidth);
1097b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            previewPopup.setHeight(popupHeight);
1098b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
1099b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mPopupPreviewX, mPopupPreviewY);
1100b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1101b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPreviewText.setVisibility(VISIBLE);
1102b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1103b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1104b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
1105b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
1106b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
1107b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * draws the cached buffer.
1108b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #invalidateKey(int)
1109b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
1110b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void invalidateAllKeys() {
1111b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDirtyRect.union(0, 0, getWidth(), getHeight());
1112b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDrawPending = true;
1113b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        invalidate();
1114b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1115b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1116b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
1117b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1118b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * one key is changing it's content. Any changes that affect the position or size of the key
1119b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * may not be honored.
1120b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param keyIndex the index of the key in the attached {@link android.inputmethodservice.Keyboard}.
1121b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @see #invalidateAllKeys
1122b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
1123b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void invalidateKey(int keyIndex) {
1124b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mKeys == null) return;
1125b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (keyIndex < 0 || keyIndex >= mKeys.length) {
1126b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return;
1127b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1128b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final Key key = mKeys[keyIndex];
1129b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mInvalidatedKey = key;
1130b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
1131b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
1132b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        onBufferDraw();
1133b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),
1134b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
1135b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1136b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1137b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void openPopupIfRequired(MotionEvent unused) {
1138b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Check if we have a popup keyboard first.
1139b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPopupKeyboardView == null || mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
1140b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return;
1141b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1142b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1143b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        Key popupKey = mKeys[mCurrentKey];
1144b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        boolean result = onLongPress(popupKey);
1145b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (result) {
1146b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mAbortKey = true;
1147b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            showPreview(NOT_A_KEY);
1148b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1149b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1150b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1151b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    /**
1152b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * Called when a key is long pressed. By default this will open any popup keyboard associated
1153b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * with this key through the attributes popupLayout and popupCharacters.
1154b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     *
1155b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @param popupKey the key that was long pressed
1156b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * @return true if the long press is handled, false otherwise. Subclasses should call the
1157b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     * method on the base class if the subclass doesn't wish to handle the call.
1158b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer     */
1159b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected boolean onLongPress(Key popupKey) {
1160b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int popupKeyboardId = popupKey.popupResId;
1161b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1162b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (popupKeyboardId != 0) {
1163b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            Keyboard keyboard;
1164b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (popupKey.popupCharacters != null) {
1165b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                keyboard = new Keyboard(getContext(), popupKeyboardId,
1166b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1167b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1168b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                keyboard = new Keyboard(getContext(), popupKeyboardId);
1169b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1170b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.setKeyboard(keyboard, mLocale);
1171b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.setVisibility(VISIBLE);
1172b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.setShifted(isShifted());
1173b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.mAnimateInAnimator.start();
1174b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.mLastSentIndex = NOT_A_KEY;
1175b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mScrimAlphaAnimator.setStartDelay(0);
1176b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mScrimAlphaAnimator.start();
1177b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardView.invalidate();
1178b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPopupKeyboardOnScreen = true;
1179b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            invalidateAllKeys();
1180b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return true;
1181b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1182b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return false;
1183b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1184b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1185b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
1186b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean onHoverEvent(MotionEvent event) {
1187b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
1188b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final int action = event.getAction();
1189b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            switch (action) {
1190b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case MotionEvent.ACTION_HOVER_ENTER: {
1191b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    event.setAction(MotionEvent.ACTION_DOWN);
1192b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } break;
1193b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case MotionEvent.ACTION_HOVER_MOVE: {
1194b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    event.setAction(MotionEvent.ACTION_MOVE);
1195b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } break;
1196b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                case MotionEvent.ACTION_HOVER_EXIT: {
1197b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    event.setAction(MotionEvent.ACTION_UP);
1198b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } break;
1199b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1200b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return onTouchEvent(event);
1201b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1202b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return true;
1203b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1204b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1205b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @SuppressLint("ClickableViewAccessibility")
1206b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
1207b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean onTouchEvent(MotionEvent me) {
1208b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Don't process touches while animating.
1209b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mAnimateInValue != MAX_ANIMATION_VALUE || mAnimateOutValue != MIN_ANIMATION_VALUE) {
1210b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return false;
1211b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1212b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1213b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Convert multi-pointer up/down events to single up/down events to
1214b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // deal with the typical multi-pointer behavior of two-thumb typing
1215b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int pointerCount = me.getPointerCount();
1216b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int action = me.getAction();
1217b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        boolean result = false;
1218b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final long now = me.getEventTime();
1219b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1220b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (pointerCount != mOldPointerCount) {
1221b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (pointerCount == 1) {
1222b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Send a down event for the latest pointer
1223b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1224b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        me.getX(), me.getY(), me.getMetaState());
1225b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                result = onModifiedTouchEvent(down, false);
1226b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                down.recycle();
1227b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // If it's an up action, then deliver the up as well.
1228b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (action == MotionEvent.ACTION_UP) {
1229b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    result = onModifiedTouchEvent(me, true);
1230b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1231b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1232b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Send an up event for the last pointer
1233b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1234b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mOldPointerX, mOldPointerY, me.getMetaState());
1235b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                result = onModifiedTouchEvent(up, true);
1236b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                up.recycle();
1237b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1238b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        } else {
1239b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (pointerCount == 1) {
1240b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                result = onModifiedTouchEvent(me, false);
1241b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mOldPointerX = me.getX();
1242b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mOldPointerY = me.getY();
1243b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1244b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // Don't do anything when 2 pointers are down and moving.
1245b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                result = true;
1246b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1247b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1248b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mOldPointerCount = pointerCount;
1249b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1250b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return result;
1251b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1252b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1253b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
1254b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int touchX = (int) me.getX() - getPaddingLeft();
1255b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int touchY = (int) me.getY() - getPaddingTop();
1256b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (touchY >= -mVerticalCorrection)
1257b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            touchY += mVerticalCorrection;
1258b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final int action = me.getAction();
1259b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final long eventTime = me.getEventTime();
1260b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        int keyIndex = getKeyIndices(touchX, touchY, null);
1261b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPossiblePoly = possiblePoly;
1262b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1263b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Track the last few movements to look for spurious swipes.
1264b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1265b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mSwipeTracker.addMovement(me);
1266b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1267b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Ignore all motion events until a DOWN.
1268b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mAbortKey
1269b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
1270b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return true;
1271b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1272b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1273b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mGestureDetector.onTouchEvent(me)) {
1274b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            showPreview(NOT_A_KEY);
1275b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mHandler.removeMessages(MSG_REPEAT);
1276b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mHandler.removeMessages(MSG_LONGPRESS);
1277b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return true;
1278b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1279b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1280b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPopupKeyboardOnScreen) {
1281b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            dismissPopupKeyboard();
1282b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return true;
1283b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1284b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1285b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        switch (action) {
1286b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            case MotionEvent.ACTION_DOWN:
1287b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mAbortKey = false;
1288b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mStartX = touchX;
1289b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mStartY = touchY;
1290b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastCodeX = touchX;
1291b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastCodeY = touchY;
1292b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastKeyTime = 0;
1293b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mCurrentKeyTime = 0;
1294b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastKey = NOT_A_KEY;
1295b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mCurrentKey = keyIndex;
1296b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mDownKey = keyIndex;
1297b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mDownTime = me.getEventTime();
1298b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastMoveTime = mDownTime;
1299b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                checkMultiTap(eventTime, keyIndex);
1300b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (keyIndex != NOT_A_KEY) {
1301b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mKeyboardActionListener.onPress(mKeys[keyIndex].codes[0]);
1302b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1303b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1304b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mRepeatKeyIndex = mCurrentKey;
1305b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    Message msg = mHandler.obtainMessage(MSG_REPEAT);
1306b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
1307b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    repeatKey();
1308b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    // Delivering the key could have caused an abort
1309b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mAbortKey) {
1310b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mRepeatKeyIndex = NOT_A_KEY;
1311b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
1312b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
1313b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1314b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mCurrentKey != NOT_A_KEY) {
1315b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1316b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1317b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1318b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                showPreview(keyIndex);
1319b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                break;
1320b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1321b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            case MotionEvent.ACTION_MOVE:
1322b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                boolean continueLongPress = false;
1323b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (keyIndex != NOT_A_KEY) {
1324b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (mCurrentKey == NOT_A_KEY) {
1325b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mCurrentKey = keyIndex;
1326b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mCurrentKeyTime = eventTime - mDownTime;
1327b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    } else {
1328b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        if (keyIndex == mCurrentKey) {
1329b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mCurrentKeyTime += eventTime - mLastMoveTime;
1330b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            continueLongPress = true;
1331b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        } else if (mRepeatKeyIndex == NOT_A_KEY) {
1332b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            resetMultiTap();
1333b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mLastKey = mCurrentKey;
1334b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mLastCodeX = mLastX;
1335b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mLastCodeY = mLastY;
1336b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mLastKeyTime =
1337b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                                    mCurrentKeyTime + eventTime - mLastMoveTime;
1338b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mCurrentKey = keyIndex;
1339b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            mCurrentKeyTime = 0;
1340b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        }
1341b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
1342b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1343b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (!continueLongPress) {
1344b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    // Cancel old longpress
1345b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mHandler.removeMessages(MSG_LONGPRESS);
1346b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    // Start new longpress if key has changed
1347b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    if (keyIndex != NOT_A_KEY) {
1348b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1349b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1350b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    }
1351b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1352b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                showPreview(mCurrentKey);
1353b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mLastMoveTime = eventTime;
1354b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                break;
1355b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1356b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            case MotionEvent.ACTION_UP:
1357b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                removeMessages();
1358b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (keyIndex == mCurrentKey) {
1359b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mCurrentKeyTime += eventTime - mLastMoveTime;
1360b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else {
1361b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    resetMultiTap();
1362b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mLastKey = mCurrentKey;
1363b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1364b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mCurrentKey = keyIndex;
1365b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mCurrentKeyTime = 0;
1366b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1367b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1368b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        && mLastKey != NOT_A_KEY) {
1369b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    mCurrentKey = mLastKey;
1370b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    touchX = mLastCodeX;
1371b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    touchY = mLastCodeY;
1372b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1373b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                showPreview(NOT_A_KEY);
1374b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                Arrays.fill(mKeyIndices, NOT_A_KEY);
1375b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                // If we're not on a repeating key (which sends on a DOWN event)
1376b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (mRepeatKeyIndex == NOT_A_KEY && !mPopupKeyboardOnScreen && !mAbortKey) {
1377b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
1378b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1379b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateKey(keyIndex);
1380b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mRepeatKeyIndex = NOT_A_KEY;
1381b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                break;
1382b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            case MotionEvent.ACTION_CANCEL:
1383b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                removeMessages();
1384b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                dismissPopupKeyboard();
1385b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mAbortKey = true;
1386b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                showPreview(NOT_A_KEY);
1387b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                invalidateKey(mCurrentKey);
1388b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                break;
1389b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1390b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mLastX = touchX;
1391b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mLastY = touchY;
1392b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return true;
1393b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1394b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1395b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private boolean repeatKey() {
1396b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        Key key = mKeys[mRepeatKeyIndex];
1397b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
1398b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return true;
1399b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1400b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1401b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected void swipeRight() {
1402b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardActionListener.swipeRight();
1403b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1404b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1405b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected void swipeLeft() {
1406b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardActionListener.swipeLeft();
1407b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1408b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1409b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected void swipeUp() {
1410b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardActionListener.swipeUp();
1411b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1412b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1413b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    protected void swipeDown() {
1414b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mKeyboardActionListener.swipeDown();
1415b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1416b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1417b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void closing() {
1418b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPreviewPopup.isShowing()) {
1419b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPreviewPopup.dismiss();
1420b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1421b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        removeMessages();
1422b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1423b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        dismissPopupKeyboard();
1424b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mBuffer = null;
1425b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mCanvas = null;
1426b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mMiniKeyboardCache.clear();
1427b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1428b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1429b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void removeMessages() {
1430b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mHandler.removeMessages(MSG_REPEAT);
1431b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mHandler.removeMessages(MSG_LONGPRESS);
1432b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mHandler.removeMessages(MSG_SHOW_PREVIEW);
1433b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1434b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1435b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
1436b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void onDetachedFromWindow() {
1437b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        super.onDetachedFromWindow();
1438b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        closing();
1439b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1440b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1441b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void dismissPopupKeyboard() {
1442b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupKeyboardOnScreen = false;
1443b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (mPopupKeyboardView != null && mPopupKeyboardView.getVisibility() == View.VISIBLE) {
1444b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (mPopupKeyboardView.mAnimateInValue == MAX_ANIMATION_VALUE) {
1445b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPopupKeyboardView.mAnimateOutAnimator.start();
1446b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mScrimAlphaAnimator.setStartDelay(ANIMATE_SCRIM_DELAY_MS);
1447b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mScrimAlphaAnimator.reverse();
1448b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1449b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mPopupKeyboardView.mAnimateInAnimator.reverse();
1450b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mScrimAlphaAnimator.setStartDelay(0);
1451b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mScrimAlphaAnimator.reverse();
1452b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1453b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1454b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1455b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        invalidateAllKeys();
1456b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1457b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1458b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public void setPopupKeyboardView(KeyboardView popupKeyboardView) {
1459b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupKeyboardView = popupKeyboardView;
1460b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mPopupKeyboardView.mBackgroundColor = getResources().getColor(R.color.car_teal_700);
1461b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1462b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1463b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void resetMultiTap() {
1464b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mLastSentIndex = NOT_A_KEY;
1465b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mTapCount = 0;
1466b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mLastTapTime = -1;
1467b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        mInMultiTap = false;
1468b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1469b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1470b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private void checkMultiTap(long eventTime, int keyIndex) {
1471b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (keyIndex == NOT_A_KEY) return;
1472b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        Key key = mKeys[keyIndex];
1473b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (key.codes.length > 1) {
1474b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mInMultiTap = true;
1475b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1476b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    && keyIndex == mLastSentIndex) {
1477b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mTapCount = (mTapCount + 1) % key.codes.length;
1478b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                return;
1479b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            } else {
1480b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                mTapCount = -1;
1481b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                return;
1482b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1483b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1484b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1485b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            resetMultiTap();
1486b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1487b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1488b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1489b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    @Override
1490b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    public boolean onGenericMotionEvent(MotionEvent event) {
1491b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        // Close the touch keyboard when the user scrolls.
1492b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
1493b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mKeyboardActionListener.stopInput();
1494b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return true;
1495b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1496b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        return false;
1497b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1498b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1499b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static class SwipeTracker {
1500b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1501b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        static final int NUM_PAST = 4;
1502b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        static final int LONGEST_PAST_TIME = 200;
1503b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1504b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final float mPastX[] = new float[NUM_PAST];
1505b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final float mPastY[] = new float[NUM_PAST];
1506b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        final long mPastTime[] = new long[NUM_PAST];
1507b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1508b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        float mYVelocity;
1509b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        float mXVelocity;
1510b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1511b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public void clear() {
1512b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mPastTime[0] = 0;
1513b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1514b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1515b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public void addMovement(MotionEvent ev) {
1516b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            long time = ev.getEventTime();
1517b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final int N = ev.getHistorySize();
1518b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            for (int i=0; i<N; i++) {
1519b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1520b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        ev.getHistoricalEventTime(i));
1521b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1522b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            addPoint(ev.getX(), ev.getY(), time);
1523b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1524b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1525b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        private void addPoint(float x, float y, long time) {
1526b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int drop = -1;
1527b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int i;
1528b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final long[] pastTime = mPastTime;
1529b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            for (i=0; i<NUM_PAST; i++) {
1530b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (pastTime[i] == 0) {
1531b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
1532b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1533b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    drop = i;
1534b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1535b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1536b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (i == NUM_PAST && drop < 0) {
1537b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                drop = 0;
1538b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1539b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (drop == i) drop--;
1540b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float[] pastX = mPastX;
1541b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float[] pastY = mPastY;
1542b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (drop >= 0) {
1543b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int start = drop+1;
1544b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int count = NUM_PAST-drop-1;
1545b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                System.arraycopy(pastX, start, pastX, 0, count);
1546b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                System.arraycopy(pastY, start, pastY, 0, count);
1547b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                System.arraycopy(pastTime, start, pastTime, 0, count);
1548b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                i -= (drop+1);
1549b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1550b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            pastX[i] = x;
1551b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            pastY[i] = y;
1552b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            pastTime[i] = time;
1553b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            i++;
1554b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (i < NUM_PAST) {
1555b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                pastTime[i] = 0;
1556b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1557b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1558b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1559b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public void computeCurrentVelocity(int units) {
1560b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            computeCurrentVelocity(units, Float.MAX_VALUE);
1561b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1562b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1563b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public void computeCurrentVelocity(int units, float maxVelocity) {
1564b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float[] pastX = mPastX;
1565b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float[] pastY = mPastY;
1566b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final long[] pastTime = mPastTime;
1567b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1568b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float oldestX = pastX[0];
1569b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final float oldestY = pastY[0];
1570b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            final long oldestTime = pastTime[0];
1571b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            float accumX = 0;
1572b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            float accumY = 0;
1573b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            int N=0;
1574b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            while (N < NUM_PAST) {
1575b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (pastTime[N] == 0) {
1576b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    break;
1577b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1578b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                N++;
1579b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1580b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1581b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            for (int i=1; i < N; i++) {
1582b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                final int dur = (int)(pastTime[i] - oldestTime);
1583b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (dur == 0) continue;
1584b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                float dist = pastX[i] - oldestX;
1585b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                float vel = (dist/dur) * units;   // pixels/frame.
1586b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (accumX == 0) accumX = vel;
1587b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                else accumX = (accumX + vel) * .5f;
1588b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1589b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                dist = pastY[i] - oldestY;
1590b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                vel = (dist/dur) * units;   // pixels/frame.
1591b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                if (accumY == 0) accumY = vel;
1592b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                else accumY = (accumY + vel) * .5f;
1593b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1594b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1595b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    : Math.min(accumX, maxVelocity);
1596b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1597b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    : Math.min(accumY, maxVelocity);
1598b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1599b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1600b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public float getXVelocity() {
1601b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return mXVelocity;
1602b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1603b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1604b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public float getYVelocity() {
1605b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            return mYVelocity;
1606b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1607b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1608b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1609b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    private static class KeyboardHander extends Handler {
1610b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        private final WeakReference<KeyboardView> mKeyboardView;
1611b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1612b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public KeyboardHander(KeyboardView keyboardView) {
1613b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            mKeyboardView = new WeakReference<KeyboardView>(keyboardView);
1614b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1615b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer
1616b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        @Override
1617b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        public void handleMessage(Message msg) {
1618b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            KeyboardView keyboardView = mKeyboardView.get();
1619b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            if (keyboardView != null) {
1620b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                switch (msg.what) {
1621b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    case MSG_SHOW_PREVIEW:
1622b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        keyboardView.showKey(msg.arg1);
1623b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
1624b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    case MSG_REMOVE_PREVIEW:
1625b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        keyboardView.mPreviewText.setVisibility(INVISIBLE);
1626b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
1627b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    case MSG_REPEAT:
1628b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        if (keyboardView.repeatKey()) {
1629b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            Message repeat = Message.obtain(this, MSG_REPEAT);
1630b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                            sendMessageDelayed(repeat, REPEAT_INTERVAL);
1631b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        }
1632b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
1633b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                    case MSG_LONGPRESS:
1634b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        keyboardView.openPopupIfRequired((MotionEvent) msg.obj);
1635b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                        break;
1636b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer                }
1637b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer            }
1638b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer        }
1639b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer    }
1640b8d05222f6f88e9c6ab64f88e9b9eba194a5c221Rakesh Iyer}
1641