PageRenderTime 58ms CodeModel.GetById 13ms app.highlight 39ms RepoModel.GetById 2ms app.codeStats 0ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/KeyboardSwitcher.java

http://eyes-free.googlecode.com/
Java | 552 lines | 430 code | 71 blank | 51 comment | 103 complexity | cef87a9071119919a250e0fdcce4264a MD5 | raw file
  1/*
  2 * Copyright (C) 2008 The Android Open Source Project
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 *
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16
 17package com.googlecode.eyesfree.inputmethod.latin;
 18
 19import android.content.SharedPreferences;
 20import android.content.res.Configuration;
 21import android.content.res.Resources;
 22import android.preference.PreferenceManager;
 23import android.view.InflateException;
 24
 25import java.lang.ref.SoftReference;
 26import java.util.Arrays;
 27import java.util.HashMap;
 28import java.util.Locale;
 29
 30public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
 31
 32    public static final int MODE_NONE = 0;
 33    public static final int MODE_TEXT = 1;
 34    public static final int MODE_SYMBOLS = 2;
 35    public static final int MODE_PHONE = 3;
 36    public static final int MODE_URL = 4;
 37    public static final int MODE_EMAIL = 5;
 38    public static final int MODE_IM = 6;
 39    public static final int MODE_WEB = 7;
 40    public static final int MODE_DPAD = 8;
 41    public static final int MODE_HIDDEN = 9;
 42
 43    // Main keyboard layouts with the settings key
 44    public static final int KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY =
 45            R.id.mode_normal_with_settings_key;
 46    public static final int KEYBOARDMODE_URL_WITH_SETTINGS_KEY =
 47            R.id.mode_url_with_settings_key;
 48    public static final int KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY =
 49            R.id.mode_email_with_settings_key;
 50    public static final int KEYBOARDMODE_IM_WITH_SETTINGS_KEY =
 51            R.id.mode_im_with_settings_key;
 52    public static final int KEYBOARDMODE_WEB_WITH_SETTINGS_KEY =
 53            R.id.mode_webentry_with_settings_key;
 54
 55    // Symbols keyboard layout with the settings key
 56    public static final int KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY =
 57            R.id.mode_symbols_with_settings_key;
 58
 59    public static final String DEFAULT_LAYOUT_ID = "0";
 60    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902";
 61    private static final int[] THEMES = new int [] { R.layout.input_gingerbread};
 62
 63    // Ids for each characters' color in the keyboard
 64    private static final int CHAR_THEME_COLOR_WHITE = 0;
 65    private static final int CHAR_THEME_COLOR_BLACK = 1;
 66
 67    // Resource ids for non-themed keyboards
 68    private static final int KBD_DPAD = R.xml.kbd_dpad;
 69    private static final int KBD_DPAD_KEYS = R.xml.kbd_dpad_keys;
 70    private static final int KBD_HIDDEN = R.xml.kbd_hidden;
 71
 72    // Tables which contains resource ids for each character theme color
 73    private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black};
 74    private static final int[] KBD_PHONE_SYMBOLS = new int[] {
 75        R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black};
 76    private static final int[] KBD_SYMBOLS = new int[] {
 77        R.xml.kbd_symbols, R.xml.kbd_symbols_black};
 78    private static final int[] KBD_SYMBOLS_SHIFT = new int[] {
 79        R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black};
 80    private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black};
 81
 82    private static final int SYMBOLS_MODE_STATE_NONE = 0;
 83    private static final int SYMBOLS_MODE_STATE_BEGIN = 1;
 84    private static final int SYMBOLS_MODE_STATE_SYMBOL = 2;
 85
 86    private LatinKeyboardView mInputView;
 87    private static final int[] ALPHABET_MODES = {
 88        KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY,
 89        KEYBOARDMODE_URL_WITH_SETTINGS_KEY,
 90        KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY,
 91        KEYBOARDMODE_IM_WITH_SETTINGS_KEY,
 92        KEYBOARDMODE_WEB_WITH_SETTINGS_KEY };
 93
 94    private final LatinIME mInputMethodService;
 95
 96    private KeyboardId mSymbolsId;
 97    private KeyboardId mSymbolsShiftedId;
 98
 99    private KeyboardId mCurrentId;
100    private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards;
101
102    private int mMode = MODE_NONE; /** One of the MODE_XXX values */
103    private int mImeOptions;
104    private boolean mIsSymbols;
105    /** mIsAutoCompletionActive indicates that auto completed word will be input instead of
106     * what user actually typed. */
107    private boolean mIsAutoCompletionActive;
108    private boolean mHasVoice;
109    private boolean mVoiceOnPrimary;
110    private boolean mPreferSymbols;
111    private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
112
113    // Indicates whether or not we show directional pad keys
114    private boolean mHasDpadKeys;
115
116    private int mLastDisplayWidth;
117    private LanguageSwitcher mLanguageSwitcher;
118    private Locale mInputLocale;
119
120    private int mLayoutId;
121
122    public KeyboardSwitcher(LatinIME ims) {
123        mInputMethodService = ims;
124
125        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims);
126        mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
127        updateDpadKeysState(prefs);
128        prefs.registerOnSharedPreferenceChangeListener(this);
129
130        mKeyboards = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
131        mSymbolsId = makeSymbolsId(false);
132        mSymbolsShiftedId = makeSymbolsShiftedId(false);
133    }
134
135    /**
136     * Sets the input locale, when there are multiple locales for input.
137     * If no locale switching is required, then the locale should be set to null.
138     * button.
139     */
140    public void setLanguageSwitcher(LanguageSwitcher languageSwitcher) {
141        mLanguageSwitcher = languageSwitcher;
142        mInputLocale = mLanguageSwitcher.getInputLocale();
143    }
144
145    private KeyboardId makeSymbolsId(boolean hasVoice) {
146        return new KeyboardId(KBD_SYMBOLS[getCharColorId()],
147                KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY,
148                false, hasVoice);
149    }
150
151    private KeyboardId makeSymbolsShiftedId(boolean hasVoice) {
152        return new KeyboardId(KBD_SYMBOLS_SHIFT[getCharColorId()],
153                KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY,
154                false, hasVoice);
155    }
156
157    public void makeKeyboards(boolean forceCreate) {
158        mSymbolsId = makeSymbolsId(mHasVoice && !mVoiceOnPrimary);
159        mSymbolsShiftedId = makeSymbolsShiftedId(mHasVoice && !mVoiceOnPrimary);
160
161        if (forceCreate) mKeyboards.clear();
162        // Configuration change is coming after the keyboard gets recreated. So don't rely on that.
163        // If keyboards have already been made, check if we have a screen width change and
164        // create the keyboard layouts again at the correct orientation
165        int displayWidth = mInputMethodService.getMaxWidth();
166        if (displayWidth == mLastDisplayWidth) return;
167        mLastDisplayWidth = displayWidth;
168        if (!forceCreate) mKeyboards.clear();
169    }
170
171    /**
172     * Represents the parameters necessary to construct a new LatinKeyboard,
173     * which also serve as a unique identifier for each keyboard type.
174     */
175    private static class KeyboardId {
176        // TODO: should have locale and portrait/landscape orientation?
177        public final int mXml;
178        public final int mKeyboardMode; /** A KEYBOARDMODE_XXX value */
179        public final boolean mEnableShiftLock;
180        public final boolean mHasVoice;
181
182        private final int mHashCode;
183
184        public KeyboardId(int xml, int mode, boolean enableShiftLock, boolean hasVoice) {
185            this.mXml = xml;
186            this.mKeyboardMode = mode;
187            this.mEnableShiftLock = enableShiftLock;
188            this.mHasVoice = hasVoice;
189
190            this.mHashCode = Arrays.hashCode(new Object[] {
191               xml, mode, enableShiftLock, hasVoice
192            });
193        }
194
195        public KeyboardId(int xml, boolean hasVoice) {
196            this(xml, 0, false, hasVoice);
197        }
198
199        @Override
200        public boolean equals(Object other) {
201            return other instanceof KeyboardId && equals((KeyboardId) other);
202        }
203
204        private boolean equals(KeyboardId other) {
205            return other.mXml == this.mXml
206                && other.mKeyboardMode == this.mKeyboardMode
207                && other.mEnableShiftLock == this.mEnableShiftLock
208                && other.mHasVoice == this.mHasVoice;
209        }
210
211        @Override
212        public int hashCode() {
213            return mHashCode;
214        }
215    }
216
217    public void setVoiceMode(boolean enableVoice, boolean voiceOnPrimary) {
218        if (enableVoice != mHasVoice || voiceOnPrimary != mVoiceOnPrimary) {
219            mKeyboards.clear();
220        }
221        mHasVoice = enableVoice;
222        mVoiceOnPrimary = voiceOnPrimary;
223        setKeyboardMode(mMode, mImeOptions, mHasVoice, mIsSymbols);
224    }
225
226    private boolean hasVoiceButton(boolean isSymbols) {
227        return mHasVoice && (isSymbols != mVoiceOnPrimary);
228    }
229
230    public void setKeyboardMode(int mode) {
231        setKeyboardMode(mode, mImeOptions, mHasVoice);
232    }
233
234    public void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) {
235        mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
236        mPreferSymbols = mode == MODE_SYMBOLS;
237        if (mode == MODE_SYMBOLS) {
238            mode = MODE_TEXT;
239        }
240        try {
241            setKeyboardMode(mode, imeOptions, enableVoice, mPreferSymbols);
242        } catch (RuntimeException e) {
243            LatinImeLogger.logOnException(mode + "," + imeOptions + "," + mPreferSymbols, e);
244        }
245    }
246
247    private void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) {
248        if (mInputView == null) return;
249
250        mMode = mode;
251        mImeOptions = imeOptions;
252        if (enableVoice != mHasVoice) {
253            // TODO clean up this unnecessary recursive call.
254            setVoiceMode(enableVoice, mVoiceOnPrimary);
255        }
256        mIsSymbols = isSymbols;
257
258        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
259        KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
260        LatinKeyboard keyboard = null;
261        keyboard = getKeyboard(id);
262
263        if (mode == MODE_PHONE) {
264            mInputView.setPhoneKeyboard(keyboard);
265        }
266
267        if (mode == MODE_DPAD) {
268            mInputView.setLongPressEnabled(true);
269        } else {
270            mInputView.setLongPressEnabled(false);
271        }
272
273        mCurrentId = id;
274        mInputView.setKeyboard(keyboard);
275        keyboard.setShifted(false);
276        keyboard.setShiftLocked(keyboard.isShiftLocked());
277        keyboard.setImeOptions(mInputMethodService.getResources(), mMode, imeOptions);
278        keyboard.setColorOfSymbolIcons(mIsAutoCompletionActive, isBlackSym());
279    }
280
281    private LatinKeyboard getKeyboard(KeyboardId id) {
282        SoftReference<LatinKeyboard> ref = mKeyboards.get(id);
283        LatinKeyboard keyboard = (ref == null) ? null : ref.get();
284        if (keyboard == null) {
285            Resources orig = mInputMethodService.getResources();
286            Configuration conf = orig.getConfiguration();
287            Locale saveLocale = conf.locale;
288            conf.locale = mInputLocale;
289            orig.updateConfiguration(conf, null);
290            keyboard = new LatinKeyboard(mInputMethodService, id.mXml, id.mKeyboardMode);
291            keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols
292                    || id.mXml == R.xml.kbd_symbols_black), mHasVoice);
293
294            if (id.mEnableShiftLock) {
295                keyboard.enableShiftLock();
296            }
297            mKeyboards.put(id, new SoftReference<LatinKeyboard>(keyboard));
298
299            conf.locale = saveLocale;
300            orig.updateConfiguration(conf, null);
301        }
302        return keyboard;
303    }
304
305    private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
306        boolean hasVoice = hasVoiceButton(isSymbols);
307        int charColorId = getCharColorId();
308        // TODO: generalize for any KeyboardId
309        int keyboardRowsResId = KBD_QWERTY[charColorId];
310        if (isSymbols) {
311            if (mode == MODE_PHONE) {
312                return new KeyboardId(KBD_PHONE_SYMBOLS[charColorId], hasVoice);
313            } else {
314                return new KeyboardId(KBD_SYMBOLS[charColorId],
315                        KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY,
316                        false, hasVoice);
317            }
318        }
319        switch (mode) {
320            case MODE_NONE:
321                LatinImeLogger.logOnWarning(
322                        "getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols);
323                // $FALL-THROUGH$
324            case MODE_TEXT:
325                return new KeyboardId(keyboardRowsResId,
326                        KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY,
327                        true, hasVoice);
328            case MODE_SYMBOLS:
329                return new KeyboardId(KBD_SYMBOLS[charColorId],
330                        KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY,
331                        false, hasVoice);
332            case MODE_PHONE:
333                return new KeyboardId(KBD_PHONE[charColorId], hasVoice);
334            case MODE_URL:
335                return new KeyboardId(keyboardRowsResId,
336                        KEYBOARDMODE_URL_WITH_SETTINGS_KEY, true, hasVoice);
337            case MODE_EMAIL:
338                return new KeyboardId(keyboardRowsResId,
339                        KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY, true, hasVoice);
340            case MODE_IM:
341                return new KeyboardId(keyboardRowsResId,
342                        KEYBOARDMODE_IM_WITH_SETTINGS_KEY, true, hasVoice);
343            case MODE_WEB:
344                return new KeyboardId(keyboardRowsResId,
345                        KEYBOARDMODE_WEB_WITH_SETTINGS_KEY, true, hasVoice);
346            case MODE_DPAD:
347                return new KeyboardId(mHasDpadKeys ? KBD_DPAD_KEYS : KBD_DPAD, hasVoice);
348            case MODE_HIDDEN:
349                return new KeyboardId(KBD_HIDDEN, hasVoice);
350        }
351        return null;
352    }
353
354    public int getKeyboardMode() {
355        return mMode;
356    }
357
358    public boolean isDpadKeysEnabled() {
359        return mHasDpadKeys;
360    }
361
362    public boolean isAlphabetMode() {
363        if (mCurrentId == null) {
364            return false;
365        }
366        int currentMode = mCurrentId.mKeyboardMode;
367        for (Integer mode : ALPHABET_MODES) {
368            if (currentMode == mode) {
369                return true;
370            }
371        }
372        return false;
373    }
374
375    public void setShifted(boolean shifted) {
376        if (mInputView != null) {
377            mInputView.setShifted(shifted);
378        }
379    }
380
381    public void setShiftLocked(boolean shiftLocked) {
382        if (mInputView != null) {
383            mInputView.setShiftLocked(shiftLocked);
384        }
385    }
386
387    public boolean isShiftedOrShiftLocked() {
388        if (mInputView != null) {
389            return mInputView.isShifted();
390        }
391        return false;
392    }
393
394    public boolean isShiftLocked() {
395        if (mInputView != null) {
396            return mInputView.isShiftLocked();
397        }
398        return false;
399    }
400
401    public void toggleShift() {
402        if (isAlphabetMode())
403            return;
404        if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) {
405            LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId);
406            mCurrentId = mSymbolsShiftedId;
407            mInputView.setKeyboard(symbolsShiftedKeyboard);
408            // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
409            // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
410            // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
411            // called.
412            symbolsShiftedKeyboard.enableShiftLock();
413            symbolsShiftedKeyboard.setShiftLocked(true);
414            symbolsShiftedKeyboard.setImeOptions(mInputMethodService.getResources(),
415                    mMode, mImeOptions);
416        } else {
417            LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId);
418            mCurrentId = mSymbolsId;
419            mInputView.setKeyboard(symbolsKeyboard);
420            // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
421            // indicator, we need to call enableShiftLock() and setShiftLocked(false).
422            symbolsKeyboard.enableShiftLock();
423            symbolsKeyboard.setShifted(false);
424            symbolsKeyboard.setImeOptions(mInputMethodService.getResources(), mMode, mImeOptions);
425        }
426    }
427
428    public void toggleSymbols() {
429        setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols);
430        if (mIsSymbols && !mPreferSymbols) {
431            mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN;
432        } else {
433            mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
434        }
435    }
436
437    public boolean isAccessibilityEnabled() {
438        return mInputView != null && mInputView.isAccessibilityEnabled();
439    }
440
441    public boolean hasDistinctMultitouch() {
442        return mInputView != null && mInputView.hasDistinctMultitouch();
443    }
444
445    /**
446     * Updates state machine to figure out when to automatically switch back to alpha mode.
447     * Returns true if the keyboard needs to switch back
448     */
449    public boolean onKey(int key) {
450        // Switch back to alpha mode if user types one or more non-space/enter characters
451        // followed by a space/enter
452        switch (mSymbolsModeState) {
453            case SYMBOLS_MODE_STATE_BEGIN:
454                if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) {
455                    mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL;
456                }
457                break;
458            case SYMBOLS_MODE_STATE_SYMBOL:
459                if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true;
460                break;
461        }
462        return false;
463    }
464
465    public LatinKeyboardView getInputView() {
466        return mInputView;
467    }
468
469    public void recreateInputView() {
470        changeLatinKeyboardView(mLayoutId, true);
471    }
472
473    private void changeLatinKeyboardView(int newLayout, boolean forceReset) {
474        if (mLayoutId != newLayout || mInputView == null || forceReset) {
475            if (mInputView != null) {
476                mInputView.closing();
477            }
478            if (THEMES.length <= newLayout) {
479                newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID);
480            }
481
482            LatinIMEUtil.GCUtils.getInstance().reset();
483            boolean tryGC = true;
484            for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
485                try {
486                    mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
487                            ).inflate(THEMES[newLayout], null);
488                    tryGC = false;
489                } catch (OutOfMemoryError e) {
490                    tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
491                            mLayoutId + "," + newLayout, e);
492                } catch (InflateException e) {
493                    tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
494                            mLayoutId + "," + newLayout, e);
495                }
496            }
497            mInputView.setOnKeyboardActionListener(mInputMethodService.mKeyboardActionListener);
498            mLayoutId = newLayout;
499        }
500        mInputMethodService.mHandler.post(new Runnable() {
501            @Override
502            public void run() {
503                synchronized (mInputMethodService) {
504                    if (mInputView != null && mInputView.getParent() == null) {
505                        mInputMethodService.setInputView(mInputView);
506                    }
507                }
508                mInputMethodService.updateInputViewShown();
509            }});
510    }
511
512    @Override
513    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
514        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
515            changeLatinKeyboardView(
516                    Integer.valueOf(sharedPreferences.getString(key, DEFAULT_LAYOUT_ID)), false);
517        } else if (LatinIMESettings.PREF_DPAD_KEYS.equals(key)) {
518            updateDpadKeysState(sharedPreferences);
519            recreateInputView();
520        }
521    }
522
523    public boolean isBlackSym () {
524        if (mInputView != null && mInputView.getSymbolColorScheme() == 1) {
525            return true;
526        }
527        return false;
528    }
529
530    private int getCharColorId () {
531        if (isBlackSym()) {
532            return CHAR_THEME_COLOR_BLACK;
533        } else {
534            return CHAR_THEME_COLOR_WHITE;
535        }
536    }
537
538    public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
539        if (isAutoCompletion != mIsAutoCompletionActive) {
540            LatinKeyboardView keyboardView = getInputView();
541            mIsAutoCompletionActive = isAutoCompletion;
542            keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
543                    .onAutoCompletionStateChanged(isAutoCompletion));
544        }
545    }
546
547    private void updateDpadKeysState(SharedPreferences prefs) {
548        Resources resources = mInputMethodService.getResources();
549        mHasDpadKeys = prefs.getBoolean(LatinIMESettings.PREF_DPAD_KEYS,
550                resources.getBoolean(R.bool.default_dpad_keys));
551    }
552}