MainKeyboardView.java revision bd93eddb52816acedd5242864e467781d4adfd71
15f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka/*
25f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * Copyright (C) 2011 The Android Open Source Project
35f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka *
45f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
55f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * you may not use this file except in compliance with the License.
65f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * You may obtain a copy of the License at
75f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka *
85f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
95f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka *
105f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
115f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
125f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * See the License for the specific language governing permissions and
145f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * limitations under the License.
155f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka */
165f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
175f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokapackage com.android.inputmethod.keyboard;
185f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
195f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.content.Context;
205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.content.pm.PackageManager;
215f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.content.res.Resources;
224112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaokaimport android.content.res.TypedArray;
236dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaokaimport android.graphics.Canvas;
244112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaokaimport android.graphics.Color;
2522b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaokaimport android.graphics.Paint;
264112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaokaimport android.graphics.Paint.Align;
27bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaokaimport android.graphics.Typeface;
284112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaokaimport android.graphics.drawable.Drawable;
295f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.os.Message;
3015d4793911fa305e0a58aced925961e948582979satokimport android.text.TextUtils;
315f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.util.AttributeSet;
325f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.util.Log;
335f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.GestureDetector;
345f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.LayoutInflater;
355f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.MotionEvent;
365f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.View;
375f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.ViewConfiguration;
38b8dc67466339dc14653ad634c86851025373326bTadashi G. Takaokaimport android.view.ViewGroup;
395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.view.accessibility.AccessibilityEvent;
405f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport android.widget.PopupWindow;
415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
425f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport com.android.inputmethod.accessibility.AccessibilityUtils;
435f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
446dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaokaimport com.android.inputmethod.deprecated.VoiceProxy;
45f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaokaimport com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
462321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaokaimport com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
476dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaokaimport com.android.inputmethod.latin.LatinIME;
4815d4793911fa305e0a58aced925961e948582979satokimport com.android.inputmethod.latin.LatinImeLogger;
495f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport com.android.inputmethod.latin.R;
505f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport com.android.inputmethod.latin.StaticInnerHandlerWrapper;
516dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaokaimport com.android.inputmethod.latin.Utils;
5215d4793911fa305e0a58aced925961e948582979satokimport com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
535f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
544112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaokaimport java.util.Locale;
555f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaokaimport java.util.WeakHashMap;
565f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
575f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka/**
585f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * A view that is responsible for detecting key presses and touch movements.
595f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka *
605f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
615f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * @attr ref R.styleable#KeyboardView_verticalCorrection
625f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka * @attr ref R.styleable#KeyboardView_popupLayout
635f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka */
64c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaokapublic class LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
65c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        SuddenJumpingTouchEventHandler.ProcessMotionEvent {
66c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka    private static final String TAG = LatinKeyboardView.class.getSimpleName();
675f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
685f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
695f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
70bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    // TODO: Kill process when the usability study mode was changed.
71bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
72bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka
73bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    /** Listener for {@link KeyboardActionListener}. */
74bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private KeyboardActionListener mKeyboardActionListener;
75bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka
76bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    /* Space key and its icons */
774112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private Key mSpaceKey;
784112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private Drawable mSpaceIcon;
79bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    // Stuff to draw language name on spacebar.
80bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private boolean mNeedsToDisplayLanguage;
81bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private Locale mSpacebarLocale;
82bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private float mSpacebarTextFadeFactor = 0.0f;
834112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private final float mSpacebarTextRatio;
844112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private float mSpacebarTextSize;
854112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private final int mSpacebarTextColor;
864112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private final int mSpacebarTextShadowColor;
874112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    // Height in space key the language name will be drawn. (proportional to space key height)
88bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
894112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    // If the full language name needs to be smaller than this value to be drawn on space key,
904112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    // its short language name will be used instead.
914112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
92bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    // Stuff to draw auto correction LED on spacebar.
93bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private boolean mAutoCorrectionSpacebarLedOn;
94bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private final boolean mAutoCorrectionSpacebarLedEnabled;
95bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private final Drawable mAutoCorrectionSpacebarLedIcon;
96bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private static final int SPACE_LED_LENGTH_PERCENT = 80;
9715d4793911fa305e0a58aced925961e948582979satok
985f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    // Mini keyboard
999d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    private PopupWindow mMoreKeysWindow;
1009d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    private MoreKeysPanel mMoreKeysPanel;
1019d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    private int mMoreKeysPanelPointerTrackerId;
1029d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
1039d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            new WeakHashMap<Key, MoreKeysPanel>();
104bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
1055f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
106bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private final boolean mIsSpacebarTriggeringPopupByLongPress;
107bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
1085f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
109bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    protected KeyDetector mKeyDetector;
11006b7c256b1992f93aab0e2cdb90f57718f0631fdTadashi G. Takaoka    private boolean mHasDistinctMultitouch;
1115f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    private int mOldPointerCount = 1;
112e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka    private Key mOldKey;
1135f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
114c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka    // To detect double tap.
1155f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    protected GestureDetector mGestureDetector;
1165f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
117f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka    private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
1185f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
119c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka    private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
1202321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaoka            implements TimerProxy {
121f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        private static final int MSG_REPEAT_KEY = 1;
122f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        private static final int MSG_LONGPRESS_KEY = 2;
12398b5c982b93cbfc74b221af30079ecb69dd4e0a1Tadashi G. Takaoka        private static final int MSG_IGNORE_DOUBLE_TAP = 3;
12493246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        private static final int MSG_KEY_TYPED = 4;
1255f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
126bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        private final int mKeyRepeatInterval;
1275f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        private boolean mInKeyRepeat;
1285f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
129c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka        public KeyTimerHandler(LatinKeyboardView outerInstance) {
1305f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            super(outerInstance);
131bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            // TODO: This should be the attribute of LatinKeyboardView.
132bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            final Resources res = outerInstance.getContext().getResources();
133bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
1345f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1355f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1365f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        @Override
1375f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public void handleMessage(Message msg) {
138c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka            final LatinKeyboardView keyboardView = getOuterInstance();
1395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            final PointerTracker tracker = (PointerTracker) msg.obj;
1405f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            switch (msg.what) {
1415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            case MSG_REPEAT_KEY:
142e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                tracker.onRepeatKey(tracker.getKey());
143bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka                startKeyRepeatTimer(mKeyRepeatInterval, tracker);
1445f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                break;
1455f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            case MSG_LONGPRESS_KEY:
146e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker);
1475f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                break;
1485f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            }
1495f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1505f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1512321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaoka        @Override
152e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka        public void startKeyRepeatTimer(long delay, PointerTracker tracker) {
1535f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            mInKeyRepeat = true;
154e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
1555f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1565f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1575f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public void cancelKeyRepeatTimer() {
1585f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            mInKeyRepeat = false;
1595f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            removeMessages(MSG_REPEAT_KEY);
1605f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1615f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1625f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public boolean isInKeyRepeat() {
1635f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return mInKeyRepeat;
1645f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1655f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1662321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaoka        @Override
167e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka        public void startLongPressTimer(long delay, PointerTracker tracker) {
16898b5c982b93cbfc74b221af30079ecb69dd4e0a1Tadashi G. Takaoka            cancelLongPressTimer();
169e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
1705f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1715f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1722321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaoka        @Override
17398b5c982b93cbfc74b221af30079ecb69dd4e0a1Tadashi G. Takaoka        public void cancelLongPressTimer() {
1745f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            removeMessages(MSG_LONGPRESS_KEY);
1755f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1765f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1772321caa1f9eb6c2d616bc36f11f5b48eebf144feTadashi G. Takaoka        @Override
17893246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        public void startKeyTypedTimer(long delay) {
17993246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka            removeMessages(MSG_KEY_TYPED);
18093246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka            sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), delay);
18193246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        }
18293246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka
18393246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        @Override
184c1f7d39b4aabe71ecf7934272a848d8c0fe5a7f0Tadashi G. Takaoka        public boolean isTyping() {
18593246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka            return hasMessages(MSG_KEY_TYPED);
18693246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        }
18793246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka
18893246652638f423d5220449f65495dea0639c750Tadashi G. Takaoka        @Override
1895f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public void cancelKeyTimers() {
1905f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            cancelKeyRepeatTimer();
19198b5c982b93cbfc74b221af30079ecb69dd4e0a1Tadashi G. Takaoka            cancelLongPressTimer();
1925f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            removeMessages(MSG_IGNORE_DOUBLE_TAP);
1935f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1945f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
1955f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public void startIgnoringDoubleTap() {
1965f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
1975f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                    ViewConfiguration.getDoubleTapTimeout());
1985f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
1995f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
2005f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public boolean isIgnoringDoubleTap() {
2015f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
2025f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
2035f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
2045f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        public void cancelAllMessages() {
2055f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            cancelKeyTimers();
2065f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
2075f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
2085f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
209bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
210c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        private boolean mProcessingShiftDoubleTapEvent = false;
211c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka
212c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        @Override
213c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        public boolean onDoubleTap(MotionEvent firstDown) {
214c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            final Keyboard keyboard = getKeyboard();
2153708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard.mId.isAlphabetKeyboard()) {
216c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                final int pointerIndex = firstDown.getActionIndex();
217c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                final int id = firstDown.getPointerId(pointerIndex);
218e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                final PointerTracker tracker = PointerTracker.getPointerTracker(
219e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                        id, LatinKeyboardView.this);
220e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY());
221c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                // If the first down event is on shift key.
222e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                if (key != null && key.isShift()) {
223c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    mProcessingShiftDoubleTapEvent = true;
224c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    return true;
225c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                }
226c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            }
227c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            mProcessingShiftDoubleTapEvent = false;
228c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            return false;
229c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        }
230c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka
231c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        @Override
232c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        public boolean onDoubleTapEvent(MotionEvent secondTap) {
233c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            if (mProcessingShiftDoubleTapEvent
234c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
235c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                final MotionEvent secondDown = secondTap;
236c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                final int pointerIndex = secondDown.getActionIndex();
237c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                final int id = secondDown.getPointerId(pointerIndex);
238e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                final PointerTracker tracker = PointerTracker.getPointerTracker(
239e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                        id, LatinKeyboardView.this);
240e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY());
241c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                // If the second down event is also on shift key.
242e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                if (key != null && key.isShift()) {
243c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    // Detected a double tap on shift key. If we are in the ignoring double tap
244c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    // mode, it means we have already turned off caps lock in
245c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    // {@link KeyboardSwitcher#onReleaseShift} .
2462ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka                    onDoubleTapShiftKey(mKeyTimerHandler.isIgnoringDoubleTap());
247c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                    return true;
248c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                }
249c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                // Otherwise these events should not be handled as double tap.
250c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                mProcessingShiftDoubleTapEvent = false;
251c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            }
252c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka            return mProcessingShiftDoubleTapEvent;
253c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        }
254c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka    }
255c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka
256c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka    public LatinKeyboardView(Context context, AttributeSet attrs) {
2575afc3ae2d9df0c2c93f2c66af13b128889ac3b5dTadashi G. Takaoka        this(context, attrs, R.attr.latinKeyboardViewStyle);
2585f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
2595f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
260c84bc3460d2fb386a1db2a2c8b135b746fa706cdTadashi G. Takaoka    public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
2615f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        super(context, attrs, defStyle);
2625f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
263c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
264c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka
2655f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final Resources res = getResources();
266bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        // TODO: This should be the attribute of LatinKeyboardView.
267f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
268f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka                R.bool.config_show_mini_keyboard_at_touched_point);
269bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        // TODO: This should be the attribute of LatinKeyboardView.
270a19b84dcf65bd70caa0fc72089cfe043b023a898Tadashi G. Takaoka        final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
271a19b84dcf65bd70caa0fc72089cfe043b023a898Tadashi G. Takaoka        mKeyDetector = new KeyDetector(keyHysteresisDistance);
2725f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
2735f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final boolean ignoreMultitouch = true;
274c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka        mGestureDetector = new GestureDetector(
275c71854a6614d1945739dcf40db61b0e887442b67Tadashi G. Takaoka                getContext(), new DoubleTapListener(), null, ignoreMultitouch);
2765f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        mGestureDetector.setIsLongpressEnabled(false);
2775f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
2785f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        mHasDistinctMultitouch = context.getPackageManager()
2795f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
280906f03121b6c6a795f35dbc24d2eceac0665f35fTadashi G. Takaoka
2815c73ed628b22fdfa59585803ee86e383c579a7d4Tadashi G. Takaoka        PointerTracker.init(mHasDistinctMultitouch, getContext());
28222b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka
283bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        // TODO: This should be the attribute of LatinKeyboardView.
28422b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka        final int longPressSpaceKeyTimeout =
28522b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka                res.getInteger(R.integer.config_long_press_space_key_timeout);
28622b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka        mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
2874112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
2884112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        final TypedArray a = context.obtainStyledAttributes(
2894112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                attrs, R.styleable.LatinKeyboardView, defStyle, R.style.LatinKeyboardView);
2904112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
2914112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedEnabled, false);
2924112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
2934112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedIcon);
2944112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarTextRatio = a.getFraction(R.styleable.LatinKeyboardView_spacebarTextRatio,
2954112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                1000, 1000, 1) / 1000.0f;
2964112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0);
2974112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarTextShadowColor = a.getColor(
2984112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0);
2994112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        a.recycle();
3005f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3015f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3025f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public void startIgnoringDoubleTap() {
3035f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
304f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka            mKeyTimerHandler.startIgnoringDoubleTap();
3055f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3065f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3075a7a696aff6718d4e0250c394a9d01cbf2a16916Tadashi G. Takaoka    public void setKeyboardActionListener(KeyboardActionListener listener) {
3085f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        mKeyboardActionListener = listener;
3095c73ed628b22fdfa59585803ee86e383c579a7d4Tadashi G. Takaoka        PointerTracker.setKeyboardActionListener(listener);
3105f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3115f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3125f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
3135f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * Returns the {@link KeyboardActionListener} object.
3145f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @return the listener attached to this keyboard
3155f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
3160efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    @Override
3170efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    public KeyboardActionListener getKeyboardActionListener() {
3185f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return mKeyboardActionListener;
3195f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
321bb4be5444b845655c0eb80bcfbb66f93603802eaTadashi G. Takaoka    @Override
3220efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    public KeyDetector getKeyDetector() {
3230efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka        return mKeyDetector;
3240efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    }
3250efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka
3260efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    @Override
327f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka    public DrawingProxy getDrawingProxy() {
328f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka        return this;
329f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka    }
330f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka
331f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka    @Override
3320efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    public TimerProxy getTimerProxy() {
3330efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka        return mKeyTimerHandler;
3340efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka    }
3350efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka
3365f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
3375f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
3385f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * view will re-layout itself to accommodate the keyboard.
3395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @see Keyboard
3405f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @see #getKeyboard()
3415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @param keyboard the keyboard to display in this view
3425f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
3435f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    @Override
3445f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public void setKeyboard(Keyboard keyboard) {
3455f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // Remove any pending messages, except dismissing preview
346f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        mKeyTimerHandler.cancelKeyTimers();
3475f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        super.setKeyboard(keyboard);
3485a7a696aff6718d4e0250c394a9d01cbf2a16916Tadashi G. Takaoka        mKeyDetector.setKeyboard(
3495a7a696aff6718d4e0250c394a9d01cbf2a16916Tadashi G. Takaoka                keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
3508da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
3515c73ed628b22fdfa59585803ee86e383c579a7d4Tadashi G. Takaoka        PointerTracker.setKeyDetector(mKeyDetector);
352c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        mTouchScreenRegulator.setKeyboard(keyboard);
3539d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        mMoreKeysPanelCache.clear();
3544112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
3554112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE);
35642fcb2de641c4cd5d57f34889c8752401e35dcc8Tadashi G. Takaoka        mSpaceIcon = keyboard.mIconsSet.getIconByAttrId(R.styleable.Keyboard_iconSpaceKey);
3574112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
3584112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
3594112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarLocale = keyboard.mId.mLocale;
3605f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3615f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3625f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
3635f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * Returns whether the device has distinct multi-touch panel.
3645f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @return true if the device has distinct multi-touch panel.
3655f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
3665f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean hasDistinctMultitouch() {
3675f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return mHasDistinctMultitouch;
3685f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3695f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
37006b7c256b1992f93aab0e2cdb90f57718f0631fdTadashi G. Takaoka    public void setDistinctMultitouch(boolean hasDistinctMultitouch) {
37106b7c256b1992f93aab0e2cdb90f57718f0631fdTadashi G. Takaoka        mHasDistinctMultitouch = hasDistinctMultitouch;
37206b7c256b1992f93aab0e2cdb90f57718f0631fdTadashi G. Takaoka    }
37306b7c256b1992f93aab0e2cdb90f57718f0631fdTadashi G. Takaoka
3745f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
3755f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
3765f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * codes for adjacent keys.  When disabled, only the primary key code will be
3775f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * reported.
3785f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @param enabled whether or not the proximity correction is enabled
3795f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
3805f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public void setProximityCorrectionEnabled(boolean enabled) {
3815f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        mKeyDetector.setProximityCorrectionEnabled(enabled);
3825f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3835f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3845f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
3855f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * Returns true if proximity correction is enabled.
3865f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
3875f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean isProximityCorrectionEnabled() {
3885f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return mKeyDetector.isProximityCorrectionEnabled();
3895f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3905f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
3915f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    @Override
3925f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public void cancelAllMessages() {
393f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        mKeyTimerHandler.cancelAllMessages();
3945f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        super.cancelAllMessages();
3955f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
3965f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
397e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka    private boolean openMiniKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
3985f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // Check if we have a popup layout specified first.
3999d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysLayout == 0) {
4005f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return false;
4015f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
4025f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
40363c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka        // Check if we are already displaying popup panel.
4049d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysPanel != null)
40563c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka            return false;
4065f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (parentKey == null)
4075f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return false;
40863c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka        return onLongPress(parentKey, tracker);
4095f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
4105f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4112ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka    private void onDoubleTapShiftKey(final boolean ignore) {
4125f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // When shift key is double tapped, the first tap is correctly processed as usual tap. And
4135f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // the second tap is treated as this double tap event, so that we need not mark tracker
414906f03121b6c6a795f35dbc24d2eceac0665f35fTadashi G. Takaoka        // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
415d999ea44805ae0a3ccac4c4f49aaf500f6c479acTadashi G. Takaoka        if (ignore) {
4162a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            invokeCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
417d999ea44805ae0a3ccac4c4f49aaf500f6c479acTadashi G. Takaoka        } else {
4182a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            invokeCodeInput(Keyboard.CODE_CAPSLOCK);
419d999ea44805ae0a3ccac4c4f49aaf500f6c479acTadashi G. Takaoka        }
4205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
4215f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4229d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    // This default implementation returns a more keys panel.
4239d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
4249d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (parentKey.mMoreKeys == null)
4255f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return null;
4265f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4279d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null);
4285f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (container == null)
4295f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            throw new NullPointerException();
4305f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4319237a72634be821c22911633ef0848130e162d58Tadashi G. Takaoka        final MiniKeyboardView miniKeyboardView =
4329237a72634be821c22911633ef0848130e162d58Tadashi G. Takaoka                (MiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
4335f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final Keyboard parentKeyboard = getKeyboard();
43432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        final Keyboard miniKeyboard = new MiniKeyboard.Builder(
4359d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                this, parentKeyboard.mMoreKeysTemplate, parentKey, parentKeyboard).build();
4365f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        miniKeyboardView.setKeyboard(miniKeyboard);
437b8dc67466339dc14653ad634c86851025373326bTadashi G. Takaoka        container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
4385f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return miniKeyboardView;
4405f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
4415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
4425f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    /**
4435f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * Called when a key is long pressed. By default this will open mini keyboard associated
4445f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * with this key.
4455f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @param parentKey the key that was long pressed
4465f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @param tracker the pointer tracker which pressed the parent key
4475f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * @return true if the long press is handled, false otherwise. Subclasses should call the
4485f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     * method on the base class if the subclass doesn't wish to handle the call.
4495f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka     */
4505f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
4516dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        final int primaryCode = parentKey.mCode;
4526dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        final Keyboard keyboard = getKeyboard();
4533708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka        if (primaryCode == Keyboard.CODE_DIGIT0 && keyboard.mId.isPhoneKeyboard()) {
4543708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            tracker.onLongPressed();
4553708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            // Long pressing on 0 in phone number keypad gives you a '+'.
4563708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            invokeCodeInput(Keyboard.CODE_PLUS);
4573708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            invokeReleaseKey(primaryCode);
4583708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            return true;
4593708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka        }
4603708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka        if (primaryCode == Keyboard.CODE_SHIFT && keyboard.mId.isAlphabetKeyboard()) {
4613708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            tracker.onLongPressed();
4623708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            invokeCodeInput(Keyboard.CODE_CAPSLOCK);
4633708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            invokeReleaseKey(primaryCode);
4643708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            return true;
4653708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka        }
4663708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka        if (primaryCode == Keyboard.CODE_SPACE) {
4673708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            // Long pressing the space key invokes IME switcher dialog.
4683708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaoka            if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
4696dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                tracker.onLongPressed();
47042e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka                invokeReleaseKey(primaryCode);
47142e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka                return true;
4726dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka            }
4736dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        }
47417dc10724bf0db04d0a4bfb2b8be0739ad9e60c6Tadashi G. Takaoka        return openMoreKeysPanel(parentKey, tracker);
4756dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    }
4766dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka
47742e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka    private boolean invokeCustomRequest(int code) {
4782a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka        return mKeyboardActionListener.onCustomRequest(code);
47942e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka    }
48042e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka
48142e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka    private void invokeCodeInput(int primaryCode) {
4822a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka        mKeyboardActionListener.onCodeInput(primaryCode, null,
4836dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
4846dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
48542e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka    }
48642e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka
48742e8c64a042476f555da5015558d51f96aaeb7fdTadashi G. Takaoka    private void invokeReleaseKey(int primaryCode) {
4882a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka        mKeyboardActionListener.onReleaseKey(primaryCode, false);
4896dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    }
4906dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka
4919d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
4929d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey);
4939d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (moreKeysPanel == null) {
4949d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            moreKeysPanel = onCreateMoreKeysPanel(parentKey);
4959d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            if (moreKeysPanel == null)
4965f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                return false;
4979d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysPanelCache.put(parentKey, moreKeysPanel);
4985f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
4999d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysWindow == null) {
5009d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysWindow = new PopupWindow(getContext());
5019d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysWindow.setBackgroundDrawable(null);
5029d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysWindow.setAnimationStyle(R.style.MiniKeyboardAnimation);
5035f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
5049d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        mMoreKeysPanel = moreKeysPanel;
5059d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        mMoreKeysPanelPointerTrackerId = tracker.mPointerId;
50663c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka
507f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka        final Keyboard keyboard = getKeyboard();
5089d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        moreKeysPanel.setShifted(keyboard.isShiftedOrShiftLocked());
509f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
510f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka                : parentKey.mX + parentKey.mWidth / 2;
511f44a01b40852dde2363a061cdc7df2ef4cb59aadTadashi G. Takaoka        final int pointY = parentKey.mY - keyboard.mVerticalGap;
5129d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        moreKeysPanel.showMoreKeysPanel(
5132a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka                this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
5149d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
5159d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
516e51d164482c7896892d6eccb80f1e1e6fe6d50dbTadashi G. Takaoka        tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
5171b087064c07975c5e2b9c17d4ca80c56e01c35c0Tadashi G. Takaoka        dimEntireKeyboard(true);
5185f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return true;
5195f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
5205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5215f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean isInSlidingKeyInput() {
5229d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysPanel != null) {
52363c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka            return true;
52463c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka        } else {
5255c73ed628b22fdfa59585803ee86e383c579a7d4Tadashi G. Takaoka            return PointerTracker.isAnyInSlidingKeyInput();
5265f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
5275f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
5285f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5295f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public int getPointerCount() {
5305f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return mOldPointerCount;
5315f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
5325f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5335f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    @Override
5345f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean onTouchEvent(MotionEvent me) {
53546286874f30c4a6ef44646c4e4adf36fe55c74b9Tadashi G. Takaoka        if (getKeyboard() == null) {
53646286874f30c4a6ef44646c4e4adf36fe55c74b9Tadashi G. Takaoka            return false;
53746286874f30c4a6ef44646c4e4adf36fe55c74b9Tadashi G. Takaoka        }
538c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka        return mTouchScreenRegulator.onTouchEvent(me);
539c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    }
540c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka
541c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    @Override
542c403a46f6d787b79768895272d53d296100677ddTadashi G. Takaoka    public boolean processMotionEvent(MotionEvent me) {
543f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
5445f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final int action = me.getActionMasked();
5455f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final int pointerCount = me.getPointerCount();
5465f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final int oldPointerCount = mOldPointerCount;
5475f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        mOldPointerCount = pointerCount;
5485f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5495f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
5505f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
5515f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // events except a transition from/to single-touch.
552f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
5535f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return true;
5545f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
5555f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5565f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
5579d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysPanel == null && mGestureDetector != null
5585f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                && mGestureDetector.onTouchEvent(me)) {
5595c73ed628b22fdfa59585803ee86e383c579a7d4Tadashi G. Takaoka            PointerTracker.dismissAllKeyPreviews();
560f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka            mKeyTimerHandler.cancelKeyTimers();
5615f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return true;
5625f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
5635f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
5645f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final long eventTime = me.getEventTime();
5655f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final int index = me.getActionIndex();
5665f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        final int id = me.getPointerId(index);
56763c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka        final int x, y;
5689d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) {
5699d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            x = mMoreKeysPanel.translateX((int)me.getX(index));
5709d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            y = mMoreKeysPanel.translateY((int)me.getY(index));
57163c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka        } else {
57263c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka            x = (int)me.getX(index);
57363c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka            y = (int)me.getY(index);
5745f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
57515d4793911fa305e0a58aced925961e948582979satok        if (ENABLE_USABILITY_STUDY_LOG) {
57615d4793911fa305e0a58aced925961e948582979satok            final String eventTag;
57715d4793911fa305e0a58aced925961e948582979satok            switch (action) {
57815d4793911fa305e0a58aced925961e948582979satok                case MotionEvent.ACTION_UP:
57915d4793911fa305e0a58aced925961e948582979satok                    eventTag = "[Up]";
58015d4793911fa305e0a58aced925961e948582979satok                    break;
58115d4793911fa305e0a58aced925961e948582979satok                case MotionEvent.ACTION_DOWN:
58215d4793911fa305e0a58aced925961e948582979satok                    eventTag = "[Down]";
58315d4793911fa305e0a58aced925961e948582979satok                    break;
58415d4793911fa305e0a58aced925961e948582979satok                case MotionEvent.ACTION_POINTER_UP:
58515d4793911fa305e0a58aced925961e948582979satok                    eventTag = "[PointerUp]";
58615d4793911fa305e0a58aced925961e948582979satok                    break;
58715d4793911fa305e0a58aced925961e948582979satok                case MotionEvent.ACTION_POINTER_DOWN:
58815d4793911fa305e0a58aced925961e948582979satok                    eventTag = "[PointerDown]";
58915d4793911fa305e0a58aced925961e948582979satok                    break;
5903425852be4229b8937c3fd9a82d709d9bd8c4b4eKen Wakasa                case MotionEvent.ACTION_MOVE: // Skip this as being logged below
5913425852be4229b8937c3fd9a82d709d9bd8c4b4eKen Wakasa                    eventTag = "";
5923425852be4229b8937c3fd9a82d709d9bd8c4b4eKen Wakasa                    break;
59315d4793911fa305e0a58aced925961e948582979satok                default:
59415d4793911fa305e0a58aced925961e948582979satok                    eventTag = "[Action" + action + "]";
59515d4793911fa305e0a58aced925961e948582979satok                    break;
59615d4793911fa305e0a58aced925961e948582979satok            }
59715d4793911fa305e0a58aced925961e948582979satok            if (!TextUtils.isEmpty(eventTag)) {
59815d4793911fa305e0a58aced925961e948582979satok                UsabilityStudyLogUtils.getInstance().write(
59915d4793911fa305e0a58aced925961e948582979satok                        eventTag + eventTime + "," + id + "," + x + "," + y + "\t\t");
60015d4793911fa305e0a58aced925961e948582979satok            }
60115d4793911fa305e0a58aced925961e948582979satok        }
6025f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
603f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        if (mKeyTimerHandler.isInKeyRepeat()) {
604e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
6055f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            // Key repeating timer will be canceled if 2 or more keys are in action, and current
6065f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            // event (UP or DOWN) is non-modifier key.
6075f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            if (pointerCount > 1 && !tracker.isModifier()) {
608f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka                mKeyTimerHandler.cancelKeyRepeatTimer();
6095f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            }
6105f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            // Up event will pass through.
6115f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
6125f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6135f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
6145f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // Translate mutli-touch event to single-touch events on the device that has no distinct
6155f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        // multi-touch panel.
616f60d09ac3086f308cafcee13ebcb94c562f9e58eTadashi G. Takaoka        if (nonDistinctMultitouch) {
6175f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            // Use only main (id=0) pointer tracker.
618e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
6195f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            if (pointerCount == 1 && oldPointerCount == 2) {
6205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                // Multi-touch to single touch transition.
6215f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                // Send a down event for the latest pointer if the key is different from the
6225f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                // previous key.
623e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                final Key newKey = tracker.getKeyOn(x, y);
624e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                if (mOldKey != newKey) {
6250efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka                    tracker.onDownEvent(x, y, eventTime, this);
6265f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                    if (action == MotionEvent.ACTION_UP)
627906f03121b6c6a795f35dbc24d2eceac0665f35fTadashi G. Takaoka                        tracker.onUpEvent(x, y, eventTime);
6285f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                }
6295f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            } else if (pointerCount == 2 && oldPointerCount == 1) {
6305f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                // Single-touch to multi-touch transition.
6315f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                // Send an up event for the last pointer.
6325f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                final int lastX = tracker.getLastX();
6335f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                final int lastY = tracker.getLastY();
634e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka                mOldKey = tracker.getKeyOn(lastX, lastY);
635906f03121b6c6a795f35dbc24d2eceac0665f35fTadashi G. Takaoka                tracker.onUpEvent(lastX, lastY, eventTime);
6365f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            } else if (pointerCount == 1 && oldPointerCount == 1) {
6378ac6d505b7ceab020a4085b3dfbea5b47362b030Tadashi G. Takaoka                tracker.processMotionEvent(action, x, y, eventTime, this);
6385f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            } else {
6395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
6405f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka                        + " (old " + oldPointerCount + ")");
6415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            }
6425f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return true;
6435f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
6445f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6455f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (action == MotionEvent.ACTION_MOVE) {
6465f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            for (int i = 0; i < pointerCount; i++) {
647e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                final PointerTracker tracker = PointerTracker.getPointerTracker(
648e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka                        me.getPointerId(i), this);
64963c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                final int px, py;
6509d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                if (mMoreKeysPanel != null
6519d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                        && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
6529d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                    px = mMoreKeysPanel.translateX((int)me.getX(i));
6539d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                    py = mMoreKeysPanel.translateY((int)me.getY(i));
65463c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                } else {
65563c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                    px = (int)me.getX(i);
65663c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                    py = (int)me.getY(i);
65763c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                }
65863c233ab9f50d844be6e52e382c6664475606760Tadashi G. Takaoka                tracker.onMoveEvent(px, py, eventTime);
65915d4793911fa305e0a58aced925961e948582979satok                if (ENABLE_USABILITY_STUDY_LOG) {
66015d4793911fa305e0a58aced925961e948582979satok                    UsabilityStudyLogUtils.getInstance().write("[Move]"  + eventTime + ","
66115d4793911fa305e0a58aced925961e948582979satok                            + me.getPointerId(i) + "," + px + "," + py + "\t\t");
66215d4793911fa305e0a58aced925961e948582979satok                }
6635f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            }
6645f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        } else {
665e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
666e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka            tracker.processMotionEvent(action, x, y, eventTime, this);
6675f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
6685f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6695f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return true;
6705f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
6715f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6725f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    @Override
6735f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public void closing() {
6745f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        super.closing();
6759d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        dismissMoreKeysPanel();
6769d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        mMoreKeysPanelCache.clear();
6775f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
6785f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6799ec80d9d89eb599329c354451acdc482cc3de836Tadashi G. Takaoka    @Override
6809d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    public boolean dismissMoreKeysPanel() {
6819d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) {
6829d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysWindow.dismiss();
6839d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysPanel = null;
6849d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeysPanelPointerTrackerId = -1;
6851b087064c07975c5e2b9c17d4ca80c56e01c35c0Tadashi G. Takaoka            dimEntireKeyboard(false);
6865f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return true;
6875f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
6885f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return false;
6895f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
6905f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6915f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean handleBack() {
6929d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        return dismissMoreKeysPanel();
6935f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
6945f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
6955f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    @Override
6966dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    public void draw(Canvas c) {
6976dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        Utils.GCUtils.getInstance().reset();
6986dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        boolean tryGC = true;
6996dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
7006dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka            try {
7016dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                super.draw(c);
7026dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                tryGC = false;
7036dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka            } catch (OutOfMemoryError e) {
7046dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
7056dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka            }
7066dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        }
7076dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    }
7086dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka
7096dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    @Override
7106dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    protected void onAttachedToWindow() {
7116dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        // Token is available from here.
7126dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka        VoiceProxy.getInstance().onAttachedToWindow();
7136dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    }
7146dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka
7156dde878d515f7bf5268d16a8fe4921d8821c5ae7Tadashi G. Takaoka    @Override
7165f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
7175f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
7185f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka            return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
7192ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka                    event) || super.dispatchPopulateAccessibilityEvent(event);
7205f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
7215f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
7225f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return super.dispatchPopulateAccessibilityEvent(event);
7235f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
7245f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
725586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette    /**
726586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * Receives hover events from the input framework. This method overrides
727586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
728586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * lower SDK versions, this method is never called.
729586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     *
730586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * @param event The motion event to be dispatched.
731586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * @return {@code true} if the event was handled by the view, {@code false}
732586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     *         otherwise
733586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     */
734586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
7355f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
736e88e1b22c87a075554fb3f10cee492e169570958Tadashi G. Takaoka            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
737586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
7385f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        }
7395f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka
740586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette        // Reflection doesn't support calling superclass methods.
7415f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka        return false;
7425f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka    }
74322b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka
7445afc3ae2d9df0c2c93f2c66af13b128889ac3b5dTadashi G. Takaoka    public void updateShortcutKey(boolean available) {
7457bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        final Keyboard keyboard = getKeyboard();
7467bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        if (keyboard == null) return;
7477bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT);
7487bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        if (shortcutKey == null) return;
7497bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        shortcutKey.setEnabled(available);
7507bd714c086a78e2058543b0971ac92f5a30b2362Tadashi G. Takaoka        invalidateKey(shortcutKey);
7515afc3ae2d9df0c2c93f2c66af13b128889ac3b5dTadashi G. Takaoka    }
7525afc3ae2d9df0c2c93f2c66af13b128889ac3b5dTadashi G. Takaoka
7534112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    public void updateSpacebar(float fadeFactor, boolean needsToDisplayLanguage) {
7544112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mSpacebarTextFadeFactor = fadeFactor;
7554112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mNeedsToDisplayLanguage = needsToDisplayLanguage;
7564112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        invalidateKey(mSpaceKey);
7574112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    }
7584112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
7594112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    public void updateAutoCorrectionState(boolean isAutoCorrection) {
7604112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        if (!mAutoCorrectionSpacebarLedEnabled) return;
7614112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        mAutoCorrectionSpacebarLedOn = isAutoCorrection;
7624112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        invalidateKey(mSpaceKey);
76322b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka    }
76422b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka
76522b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka    @Override
766f9521c6f378e3f2aa13d9e382ae13708e3ae6317Tadashi G. Takaoka    protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
76722b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka        super.onDrawKeyTopVisuals(key, canvas, paint, params);
76822b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka
7694112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        if (key.mCode == Keyboard.CODE_SPACE) {
770bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            drawSpacebar(key, canvas, paint);
771bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka
7724112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            // Whether space key needs to show the "..." popup hint for special purposes
7734112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            if (mIsSpacebarTriggeringPopupByLongPress
7744112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                    && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
775bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka                drawKeyPopupHint(key, canvas, paint, params);
7764112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            }
77722b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka        }
77822b48de11ce6f31a0edf90e1308073e67a7a2adbTadashi G. Takaoka    }
7794112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
7804112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    private static int getSpacebarTextColor(int color, float fadeFactor) {
7814112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
7824112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                Color.red(color), Color.green(color), Color.blue(color));
7834112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        return newColor;
7844112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    }
7854112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
7864112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    // Compute width of text with specified text size using paint.
787bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private int getTextWidth(Paint paint, String text, float textSize) {
7884112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        paint.setTextSize(textSize);
789bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        return (int)getLabelWidth(text, paint);
7904112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    }
7914112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
7924015a64a263b07178b99d3e3e864bd75da017638Tadashi G. Takaoka    // Layout locale language name on spacebar.
793bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private String layoutLanguageOnSpacebar(Paint paint, Locale locale, int width,
7944112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            float origTextSize) {
795bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        paint.setTextAlign(Align.CENTER);
796bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        paint.setTypeface(Typeface.DEFAULT);
7974112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // Estimate appropriate language name text size to fit in maxTextWidth.
7984112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        String language = Utils.getFullDisplayName(locale, true);
799bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        int textWidth = getTextWidth(paint, language, origTextSize);
8004112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // Assuming text width and text size are proportional to each other.
8014112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
8024112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // allow variable text size
803bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        textWidth = getTextWidth(paint, language, textSize);
8044112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // If text size goes too small or text does not fit, use middle or short name
8054112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
8064112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                || (textWidth > width);
8074112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
8084112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        final boolean useShortName;
8094112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        if (useMiddleName) {
8104112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            language = Utils.getMiddleDisplayLanguage(locale);
811bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            textWidth = getTextWidth(paint, language, origTextSize);
8124112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
8134112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
8144112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                    || (textWidth > width);
8154112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        } else {
8164112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            useShortName = false;
8174112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        }
8184112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
8194112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        if (useShortName) {
8204112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            language = Utils.getShortDisplayLanguage(locale);
821bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            textWidth = getTextWidth(paint, language, origTextSize);
8224112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
8234112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        }
8244112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        paint.setTextSize(textSize);
8254112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
8264112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        return language;
8274112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    }
8284112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
829bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka    private void drawSpacebar(Key key, Canvas canvas, Paint paint) {
830bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        final int width = key.mWidth;
831bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : key.mHeight;
8324112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
8334112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // If application locales are explicitly selected.
834bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        if (mNeedsToDisplayLanguage) {
835bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
836bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka                    mSpacebarTextSize);
8374112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            // Draw language text with shadow
8384112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            // In case there is no space icon, we will place the language text at the center of
8394112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            // spacebar.
8404112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final float descent = paint.descent();
8414112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final float textHeight = -paint.ascent() + descent;
8424112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
8434112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka                    : height / 2 + textHeight / 2;
844bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor));
8454112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
846bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor));
8474112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            canvas.drawText(language, width / 2, baseline - descent, paint);
8484112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        }
8494112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka
8504112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        // Draw the spacebar icon at the bottom
851bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka        if (mAutoCorrectionSpacebarLedOn) {
8524112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
8534112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
8544112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            int x = (width - iconWidth) / 2;
8554112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            int y = height - iconHeight;
856bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
8574112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        } else if (mSpaceIcon != null) {
8584112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final int iconWidth = mSpaceIcon.getIntrinsicWidth();
8594112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            final int iconHeight = mSpaceIcon.getIntrinsicHeight();
8604112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            int x = (width - iconWidth) / 2;
8614112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka            int y = height - iconHeight;
862bd93eddb52816acedd5242864e467781d4adfd71Tadashi G. Takaoka            drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
8634112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka        }
8644112dc05002d7a880e558418639cf25c4bd02a5aTadashi G. Takaoka    }
8655f6816fa8bf259f0340a3d12c551d1532f647d66Tadashi G. Takaoka}
866