diff options
Diffstat (limited to 'java/src')
9 files changed, 324 insertions, 220 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index ebd61505d..3a9423f4b 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -101,7 +101,7 @@ public class Key { /** Text to output when pressed. This can be multiple characters, like ".com" */ public final CharSequence mOutputText; /** More keys */ - public final CharSequence[] mMoreKeys; + public final String[] mMoreKeys; /** More keys maximum column number */ public final int mMaxMoreKeysColumn; @@ -166,7 +166,12 @@ public class Key { } private static Drawable getIcon(Keyboard.Params params, String moreKeySpec) { - return params.mIconsSet.getIconByIconId(MoreKeySpecParser.getIconId(moreKeySpec)); + final int iconAttrId = MoreKeySpecParser.getIconAttrId(moreKeySpec); + if (iconAttrId == KeyboardIconsSet.ICON_UNDEFINED) { + return null; + } else { + return params.mIconsSet.getIconByAttrId(iconAttrId); + } } /** @@ -255,7 +260,7 @@ public class Key { // Update row to have current x coordinate. row.setXPos(keyXPos + keyWidth); - final CharSequence[] moreKeys = style.getTextArray(keyAttr, + final String[] moreKeys = style.getTextArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of // config_digit_more_keys_enabled. @@ -277,12 +282,15 @@ public class Key { R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0); mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0); - mPreviewIcon = iconsSet.getIconByIconId(style.getInt(keyAttr, + final int previewIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED)); - mIcon = iconsSet.getIconByIconId(style.getInt(keyAttr, + mPreviewIcon = iconsSet.getIconByAttrId(previewIconAttrId); + final int iconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED)); - mDisabledIcon = iconsSet.getIconByIconId(style.getInt(keyAttr, + mIcon = iconsSet.getIconByAttrId(iconAttrId); + final int disabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED)); + mDisabledIcon = iconsSet.getIconByAttrId(disabledIconAttrId); mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 22ab3e4e5..4967a5e80 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -137,8 +137,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions, mKeyboardSet = builder.build(); final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId(); try { - mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols), - hasDistinctMultitouch()); + mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); } catch (RuntimeException e) { Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e); LatinImeLogger.logOnException(mainKeyboardId.toString(), e); @@ -316,7 +315,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions, } /** - * Updates state machine to figure out when to automatically snap back to the previous mode. + * Updates state machine to figure out when to automatically switch back to the previous mode. */ public void onCodeInput(int code) { mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState()); diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java index 548b5ea85..974291373 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java @@ -34,7 +34,7 @@ public class MiniKeyboard extends Keyboard { } public static class Builder extends Keyboard.Builder<Builder.MiniKeyboardParams> { - private final CharSequence[] mMoreKeys; + private final String[] mMoreKeys; public static class MiniKeyboardParams extends Keyboard.Params { /* package */int mTopRowAdjustment; @@ -230,16 +230,14 @@ public class MiniKeyboard extends Keyboard { parentKey.mX + (mParams.mDefaultKeyWidth - width) / 2, view.getMeasuredWidth()); } - private static int getMaxKeyWidth(KeyboardView view, CharSequence[] moreKeys, - int minKeyWidth) { + private static int getMaxKeyWidth(KeyboardView view, String[] moreKeys, int minKeyWidth) { final int padding = (int) view.getContext().getResources() .getDimension(R.dimen.mini_keyboard_key_horizontal_padding); Paint paint = null; int maxWidth = minKeyWidth; - for (CharSequence moreKeySpec : moreKeys) { - final CharSequence label = MoreKeySpecParser.getLabel(moreKeySpec.toString()); - // If the label is single letter, minKeyWidth is enough to hold - // the label. + for (String moreKeySpec : moreKeys) { + final String label = MoreKeySpecParser.getLabel(moreKeySpec); + // If the label is single letter, minKeyWidth is enough to hold the label. if (label != null && label.length() > 1) { if (paint == null) { paint = new Paint(); @@ -258,7 +256,7 @@ public class MiniKeyboard extends Keyboard { public MiniKeyboard build() { final MiniKeyboardParams params = mParams; for (int n = 0; n < mMoreKeys.length; n++) { - final String moreKeySpec = mMoreKeys[n].toString(); + final String moreKeySpec = mMoreKeys[n]; final int row = n / params.mNumColumns; final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row), params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java index 5dd8340fc..faea38941 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java @@ -16,11 +16,13 @@ package com.android.inputmethod.keyboard.internal; +import android.content.res.Resources; import android.content.res.TypedArray; import android.util.Log; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; @@ -30,7 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; public class KeyStyles { - private static final String TAG = "KeyStyles"; + private static final String TAG = KeyStyles.class.getSimpleName(); private static final boolean DEBUG = false; private final HashMap<String, DeclaredKeyStyle> mStyles = @@ -38,19 +40,19 @@ public class KeyStyles { private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); public interface KeyStyle { - public CharSequence[] getTextArray(TypedArray a, int index); + public String[] getTextArray(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); public int getInt(TypedArray a, int index, int defaultValue); public int getFlag(TypedArray a, int index, int defaultValue); } - /* package */ static class EmptyKeyStyle implements KeyStyle { + private static class EmptyKeyStyle implements KeyStyle { EmptyKeyStyle() { // Nothing to do. } @Override - public CharSequence[] getTextArray(TypedArray a, int index) { + public String[] getTextArray(TypedArray a, int index) { return parseTextArray(a, index); } @@ -69,64 +71,77 @@ public class KeyStyles { return a.getInt(index, defaultValue); } - protected static CharSequence[] parseTextArray(TypedArray a, int index) { + protected static String[] parseTextArray(TypedArray a, int index) { if (!a.hasValue(index)) return null; final CharSequence text = a.getText(index); - return parseCsvText(text); - } - - /* package */ static CharSequence[] parseCsvText(CharSequence text) { - final int size = text.length(); - if (size == 0) return null; - if (size == 1) return new CharSequence[] { text }; - final StringBuilder sb = new StringBuilder(); - ArrayList<CharSequence> list = null; - int start = 0; - for (int pos = 0; pos < size; pos++) { - final char c = text.charAt(pos); - if (c == ',') { - if (list == null) list = new ArrayList<CharSequence>(); - if (sb.length() == 0) { - list.add(text.subSequence(start, pos)); - } else { - list.add(sb.toString()); - sb.setLength(0); + return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name); + } + } + + /* package for test */ + static String[] parseCsvText(String rawText, Resources res, int packageNameResId) { + final String text = Utils.resolveStringResource(rawText, res, packageNameResId); + final int size = text.length(); + if (size == 0) { + return null; + } + if (size == 1) { + return new String[] { text }; + } + + final StringBuilder sb = new StringBuilder(); + ArrayList<String> list = null; + int start = 0; + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == ',') { + if (list == null) { + list = new ArrayList<String>(); + } + if (sb.length() == 0) { + list.add(text.substring(start, pos)); + } else { + list.add(sb.toString()); + sb.setLength(0); + } + start = pos + 1; + continue; + } else if (c == Utils.ESCAPE_CHAR) { + if (start == pos) { + // Skip escape character at the beginning of the value. + start++; + pos++; + } else { + if (start < pos && sb.length() == 0) { + sb.append(text.subSequence(start, pos)); } - start = pos + 1; - continue; - } else if (c == '\\') { - if (start == pos) { - // Skip escape character at the beginning of the value. - start++; - pos++; - } else { - if (start < pos && sb.length() == 0) - sb.append(text.subSequence(start, pos)); - pos++; - if (pos < size) - sb.append(text.charAt(pos)); + pos++; + if (pos < size) { + sb.append(text.charAt(pos)); } - } else if (sb.length() > 0) { - sb.append(c); } + } else if (sb.length() > 0) { + sb.append(c); } - if (list == null) { - return new CharSequence[] { sb.length() > 0 ? sb : text.subSequence(start, size) }; - } else { - list.add(sb.length() > 0 ? sb : text.subSequence(start, size)); - return list.toArray(new CharSequence[list.size()]); - } + } + if (list == null) { + return new String[] { + sb.length() > 0 ? sb.toString() : text.substring(start) + }; + } else { + list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); + return list.toArray(new String[list.size()]); } } - /* package */ static class DeclaredKeyStyle extends EmptyKeyStyle { + private static class DeclaredKeyStyle extends EmptyKeyStyle { private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>(); @Override - public CharSequence[] getTextArray(TypedArray a, int index) { + public String[] getTextArray(TypedArray a, int index) { return a.hasValue(index) - ? super.getTextArray(a, index) : (CharSequence[])mAttributes.get(index); + ? super.getTextArray(a, index) : (String[])mAttributes.get(index); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index 6313a61b5..09ecbcaa0 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -31,34 +31,36 @@ public class KeyboardIconsSet { private static final String TAG = KeyboardIconsSet.class.getSimpleName(); public static final int ICON_UNDEFINED = 0; + private static final int ATTR_UNDEFINED = 0; private final Map<Integer, Drawable> mIcons = new HashMap<Integer, Drawable>(); // The key value should be aligned with the enum value of Keyboard.icon*. private static final Map<Integer, Integer> ICONS_TO_ATTRS_MAP = new HashMap<Integer, Integer>(); + private static final Map<String, Integer> NAME_TO_ATTRS_MAP = new HashMap<String, Integer>(); private static final Collection<Integer> VALID_ATTRS; static { - addIconIdMap(1, R.styleable.Keyboard_iconShiftKey); - addIconIdMap(2, R.styleable.Keyboard_iconDeleteKey); - // This is also represented as "@icon/3" in keyboard layout XML. - addIconIdMap(3, R.styleable.Keyboard_iconSettingsKey); - addIconIdMap(4, R.styleable.Keyboard_iconSpaceKey); - addIconIdMap(5, R.styleable.Keyboard_iconReturnKey); - addIconIdMap(6, R.styleable.Keyboard_iconSearchKey); - // This is also represented as "@icon/7" in keyboard layout XML. - addIconIdMap(7, R.styleable.Keyboard_iconTabKey); - addIconIdMap(8, R.styleable.Keyboard_iconShortcutKey); - addIconIdMap(9, R.styleable.Keyboard_iconShortcutForLabel); - addIconIdMap(10, R.styleable.Keyboard_iconSpaceKeyForNumberLayout); - addIconIdMap(11, R.styleable.Keyboard_iconShiftKeyShifted); - addIconIdMap(12, R.styleable.Keyboard_iconDisabledShortcutKey); - addIconIdMap(13, R.styleable.Keyboard_iconPreviewTabKey); + addIconIdMap(1, "shiftKey", R.styleable.Keyboard_iconShiftKey); + addIconIdMap(2, "deleteKey", R.styleable.Keyboard_iconDeleteKey); + addIconIdMap(3, "settingsKey", R.styleable.Keyboard_iconSettingsKey); + addIconIdMap(4, "spaceKey", R.styleable.Keyboard_iconSpaceKey); + addIconIdMap(5, "returnKey", R.styleable.Keyboard_iconReturnKey); + addIconIdMap(6, "searchKey", R.styleable.Keyboard_iconSearchKey); + addIconIdMap(7, "tabKey", R.styleable.Keyboard_iconTabKey); + addIconIdMap(8, "shortcutKey", R.styleable.Keyboard_iconShortcutKey); + addIconIdMap(9, "shortcutForLabel", R.styleable.Keyboard_iconShortcutForLabel); + addIconIdMap(10, "spaceKeyForNumberLayout", + R.styleable.Keyboard_iconSpaceKeyForNumberLayout); + addIconIdMap(11, "shiftKeyShifted", R.styleable.Keyboard_iconShiftKeyShifted); + addIconIdMap(12, "disabledShortcurKey", R.styleable.Keyboard_iconDisabledShortcutKey); + addIconIdMap(13, "previewTabKey", R.styleable.Keyboard_iconPreviewTabKey); VALID_ATTRS = ICONS_TO_ATTRS_MAP.values(); } - private static void addIconIdMap(int iconId, int attrId) { + private static void addIconIdMap(int iconId, String name, Integer attrId) { ICONS_TO_ATTRS_MAP.put(iconId, attrId); + NAME_TO_ATTRS_MAP.put(name, attrId); } public void loadIcons(final TypedArray keyboardAttrs) { @@ -76,18 +78,29 @@ public class KeyboardIconsSet { } } - public Drawable getIconByIconId(final Integer iconId) { + public static int getIconAttrId(final Integer iconId) { if (iconId == ICON_UNDEFINED) { - return null; + return ATTR_UNDEFINED; } final Integer attrId = ICONS_TO_ATTRS_MAP.get(iconId); if (attrId == null) { throw new IllegalArgumentException("icon id is out of range: " + iconId); } - return getIconByAttrId(attrId); + return attrId; + } + + public static int getIconAttrId(final String iconName) { + final Integer attrId = NAME_TO_ATTRS_MAP.get(iconName); + if (attrId == null) { + throw new IllegalArgumentException("unknown icon name: " + iconName); + } + return attrId; } public Drawable getIconByAttrId(final Integer attrId) { + if (attrId == ATTR_UNDEFINED) { + return null; + } if (!VALID_ATTRS.contains(attrId)) { throw new IllegalArgumentException("unknown icon attribute id: " + attrId); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index c43b9852b..af16e4907 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -26,7 +26,7 @@ import com.android.inputmethod.keyboard.Keyboard; * * This class contains all keyboard state transition logic. * - * The input events are {@link #onLoadKeyboard(String, boolean)}, {@link #onSaveKeyboardState()}, + * The input events are {@link #onLoadKeyboard(String)}, {@link #onSaveKeyboardState()}, * {@link #onPressKey(int)}, {@link #onReleaseKey(int, boolean)}, * {@link #onCodeInput(int, boolean, boolean)}, {@link #onCancelInput(boolean)}, * {@link #onUpdateShiftState(boolean)}. @@ -58,7 +58,7 @@ public class KeyboardState { public void requestUpdatingShiftState(); } - private KeyboardShiftState mKeyboardShiftState = new KeyboardShiftState(); + private final SwitchActions mSwitchActions; private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); @@ -72,17 +72,15 @@ public class KeyboardState { private static final int SWITCH_STATE_CHORDING_ALPHA = 5; private static final int SWITCH_STATE_CHORDING_SYMBOL = 6; private int mSwitchState = SWITCH_STATE_ALPHA; - private String mLayoutSwitchBackSymbols; - private boolean mHasDistinctMultitouch; - - private final SwitchActions mSwitchActions; private boolean mIsAlphabetMode; + private KeyboardShiftState mAlphabetShiftState = new KeyboardShiftState(); private boolean mIsSymbolShifted; + private boolean mPrevMainKeyboardWasShiftLocked; + private boolean mPrevSymbolsKeyboardWasShifted; private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState(); - private boolean mPrevMainKeyboardWasShiftLocked; static class SavedKeyboardState { public boolean mIsValid; @@ -95,17 +93,17 @@ public class KeyboardState { mSwitchActions = switchActions; } - public void onLoadKeyboard(String layoutSwitchBackSymbols, boolean hasDistinctMultitouch) { + public void onLoadKeyboard(String layoutSwitchBackSymbols) { if (DEBUG_EVENT) { Log.d(TAG, "onLoadKeyboard"); } mLayoutSwitchBackSymbols = layoutSwitchBackSymbols; - mHasDistinctMultitouch = hasDistinctMultitouch; - mKeyboardShiftState.setShifted(false); - mKeyboardShiftState.setShiftLocked(false); + // Reset alphabet shift state. + mAlphabetShiftState.setShiftLocked(false); + mPrevMainKeyboardWasShiftLocked = false; + mPrevSymbolsKeyboardWasShifted = false; mShiftKeyState.onRelease(); mSymbolKeyState.onRelease(); - mPrevMainKeyboardWasShiftLocked = false; onRestoreKeyboardState(); } @@ -113,9 +111,9 @@ public class KeyboardState { final SavedKeyboardState state = mSavedKeyboardState; state.mIsAlphabetMode = mIsAlphabetMode; if (mIsAlphabetMode) { - state.mIsShiftLocked = mKeyboardShiftState.isShiftLocked(); + state.mIsShiftLocked = mAlphabetShiftState.isShiftLocked(); state.mIsShifted = !state.mIsShiftLocked - && mKeyboardShiftState.isShiftedOrShiftLocked(); + && mAlphabetShiftState.isShiftedOrShiftLocked(); } else { state.mIsShiftLocked = false; state.mIsShifted = mIsSymbolShifted; @@ -157,25 +155,23 @@ public class KeyboardState { // TODO: Remove this method. public boolean isShiftLocked() { - return mKeyboardShiftState.isShiftLocked(); + return mAlphabetShiftState.isShiftLocked(); } private void setShifted(int shiftMode) { if (DEBUG_ACTION) { Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode)); } - if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) { - mKeyboardShiftState.setAutomaticTemporaryUpperCase(); - } else { - final boolean shifted = (shiftMode == SwitchActions.MANUAL_SHIFT); - // On non-distinct multi touch panel device, we should also turn off the shift locked - // state when shift key is pressed to go to normal mode. - // On the other hand, on distinct multi touch panel device, turning off the shift - // locked state with shift key pressing is handled by onReleaseShift(). - if (!mHasDistinctMultitouch && !shifted && mKeyboardShiftState.isShiftLocked()) { - mSwitchActions.setShiftLocked(false); - } - mKeyboardShiftState.setShifted(shifted); + switch (shiftMode) { + case SwitchActions.AUTOMATIC_SHIFT: + mAlphabetShiftState.setAutomaticTemporaryUpperCase(); + break; + case SwitchActions.MANUAL_SHIFT: + mAlphabetShiftState.setShifted(true); + break; + case SwitchActions.UNSHIFT: + mAlphabetShiftState.setShifted(false); + break; } mSwitchActions.setShifted(shiftMode); } @@ -184,7 +180,7 @@ public class KeyboardState { if (DEBUG_ACTION) { Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked); } - mKeyboardShiftState.setShiftLocked(shiftLocked); + mAlphabetShiftState.setShiftLocked(shiftLocked); mSwitchActions.setShiftLocked(shiftLocked); } @@ -208,6 +204,7 @@ public class KeyboardState { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetKeyboard"); } + mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; mSwitchActions.setAlphabetKeyboard(); mIsAlphabetMode = true; mIsSymbolShifted = false; @@ -219,13 +216,21 @@ public class KeyboardState { // TODO: Make this method private public void setSymbolsKeyboard() { + mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); + if (mPrevSymbolsKeyboardWasShifted) { + setSymbolsShiftedKeyboard(); + return; + } + if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsKeyboard"); } - mPrevMainKeyboardWasShiftLocked = mKeyboardShiftState.isShiftLocked(); mSwitchActions.setSymbolsKeyboard(); mIsAlphabetMode = false; mIsSymbolShifted = false; + // Reset alphabet shift state. + mAlphabetShiftState.setShiftLocked(false); + mPrevSymbolsKeyboardWasShifted = false; mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } @@ -236,6 +241,9 @@ public class KeyboardState { mSwitchActions.setSymbolsShiftedKeyboard(); mIsAlphabetMode = false; mIsSymbolShifted = true; + // Reset alphabet shift state. + mAlphabetShiftState.setShiftLocked(false); + mPrevSymbolsKeyboardWasShifted = false; mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } @@ -273,7 +281,7 @@ public class KeyboardState { } private void onReleaseSymbol() { - // Snap back to the previous keyboard mode if the user chords the mode change key and + // Switch back to the previous keyboard mode if the user chords the mode change key and // another key, then releases the mode change key. if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) { toggleAlphabetAndSymbols(); @@ -290,7 +298,7 @@ public class KeyboardState { private void onUpdateShiftStateInternal(boolean autoCaps) { if (mIsAlphabetMode) { - if (!mKeyboardShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { + if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { if (mShiftKeyState.isReleasing() && autoCaps) { // Only when shift key is releasing, automatic temporary upper case will be set. setShifted(SwitchActions.AUTOMATIC_SHIFT); @@ -308,17 +316,17 @@ public class KeyboardState { private void onPressShift() { if (mIsAlphabetMode) { - if (mKeyboardShiftState.isShiftLocked()) { + if (mAlphabetShiftState.isShiftLocked()) { // Shift key is pressed while caps lock state, we will treat this state as shifted // caps lock state and mark as if shift key pressed while normal state. setShifted(SwitchActions.MANUAL_SHIFT); mShiftKeyState.onPress(); - } else if (mKeyboardShiftState.isAutomaticTemporaryUpperCase()) { + } else if (mAlphabetShiftState.isAutomaticTemporaryUpperCase()) { // Shift key is pressed while automatic temporary upper case, we have to move to // manual temporary upper case. setShifted(SwitchActions.MANUAL_SHIFT); mShiftKeyState.onPress(); - } else if (mKeyboardShiftState.isShiftedOrShiftLocked()) { + } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) { // In manual upper case state, we just record shift key has been pressing while // shifted state. mShiftKeyState.onPressOnShifted(); @@ -337,30 +345,34 @@ public class KeyboardState { private void onReleaseShift(boolean withSliding) { if (mIsAlphabetMode) { - final boolean isShiftLocked = mKeyboardShiftState.isShiftLocked(); + final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); if (mShiftKeyState.isMomentary()) { // After chording input while normal state. - setShifted(SwitchActions.UNSHIFT); - } else if (isShiftLocked && !mKeyboardShiftState.isShiftLockShifted() + if (mAlphabetShiftState.isShiftLockShifted()) { + setShiftLocked(true); + } else { + setShifted(SwitchActions.UNSHIFT); + } + } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) && !withSliding) { // Shift has been long pressed, ignore this release. } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { // Shift has been pressed without chording while caps lock state. setShiftLocked(false); - } else if (mKeyboardShiftState.isShiftedOrShiftLocked() + } else if (mAlphabetShiftState.isShiftedOrShiftLocked() && mShiftKeyState.isPressingOnShifted() && !withSliding) { // Shift has been pressed without chording while shifted state. setShifted(SwitchActions.UNSHIFT); - } else if (mKeyboardShiftState.isManualTemporaryUpperCaseFromAuto() + } else if (mAlphabetShiftState.isManualTemporaryUpperCaseFromAuto() && mShiftKeyState.isPressing() && !withSliding) { // Shift has been pressed without chording while manual temporary upper case // transited from automatic temporary upper case. setShifted(SwitchActions.UNSHIFT); } } else { - // In symbol mode, snap back to the previous keyboard mode if the user chords the shift - // key and another key, then releases the shift key. + // In symbol mode, switch back to the previous keyboard mode if the user chords the + // shift key and another key, then releases the shift key. if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) { toggleShiftInSymbols(); } @@ -372,7 +384,7 @@ public class KeyboardState { if (DEBUG_EVENT) { Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this); } - // Snap back to the previous keyboard mode if the user cancels sliding input. + // Switch back to the previous keyboard mode if the user cancels sliding input. if (isSinglePointer) { if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { toggleAlphabetAndSymbols(); @@ -405,7 +417,7 @@ public class KeyboardState { } if (mIsAlphabetMode && code == Keyboard.CODE_CAPSLOCK) { - if (mKeyboardShiftState.isShiftLocked()) { + if (mAlphabetShiftState.isShiftLocked()) { setShiftLocked(false); // Shift key is long pressed or double tapped while caps lock state, we will // toggle back to normal state. And mark as if shift key is released. @@ -431,13 +443,13 @@ public class KeyboardState { mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } } else if (isSinglePointer) { - // Snap back to the previous keyboard mode if the user pressed the mode change key + // Switch back to the previous keyboard mode if the user pressed the mode change key // and slid to other key, then released the finger. - // If the user cancels the sliding input, snapping back to the previous keyboard + // If the user cancels the sliding input, switching back to the previous keyboard // mode is handled by {@link #onCancelInput}. toggleAlphabetAndSymbols(); } else { - // Chording input is being started. The keyboard mode will be snapped back to the + // Chording input is being started. The keyboard mode will be switched back to the // previous mode in {@link onReleaseSymbol} when the mode change key is released. mSwitchState = SWITCH_STATE_CHORDING_ALPHA; } @@ -447,12 +459,12 @@ public class KeyboardState { // Detected only the shift key has been pressed on symbol layout, and then released. mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } else if (isSinglePointer) { - // Snap back to the previous keyboard mode if the user pressed the shift key on + // Switch back to the previous keyboard mode if the user pressed the shift key on // symbol mode and slid to other key, then released the finger. toggleShiftInSymbols(); mSwitchState = SWITCH_STATE_SYMBOL; } else { - // Chording input is being started. The keyboard mode will be snapped back to the + // Chording input is being started. The keyboard mode will be switched back to the // previous mode in {@link onReleaseShift} when the shift key is released. mSwitchState = SWITCH_STATE_CHORDING_SYMBOL; } @@ -462,14 +474,14 @@ public class KeyboardState { || code == Keyboard.CODE_OUTPUT_TEXT)) { mSwitchState = SWITCH_STATE_SYMBOL; } - // Snap back to alpha keyboard mode immediately if user types a quote character. + // Switch back to alpha keyboard mode immediately if user types a quote character. if (isLayoutSwitchBackCharacter(code)) { setAlphabetKeyboard(); } break; case SWITCH_STATE_SYMBOL: case SWITCH_STATE_CHORDING_SYMBOL: - // Snap back to alpha keyboard mode if user types one or more non-space/enter + // Switch back to alpha keyboard mode if user types one or more non-space/enter // characters followed by a space/enter or a quote character. if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) { setAlphabetKeyboard(); @@ -507,7 +519,8 @@ public class KeyboardState { @Override public String toString() { - return "[keyboard=" + mKeyboardShiftState + return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() + : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + " shift=" + mShiftKeyState + " symbol=" + mSymbolKeyState + " switch=" + switchStateToString(mSwitchState) + "]"; diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java index 93be31ed9..16777733e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java @@ -18,10 +18,10 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.Resources; import android.text.TextUtils; -import android.util.Log; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.Utils; import java.util.ArrayList; @@ -31,20 +31,16 @@ import java.util.ArrayList; * Each "more key" specification is one of the following: * - A single letter (Letter) * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText). - * - Icon followed by keyOutputText or code (@icon/icon_number|@integer/key_code) + * - Icon followed by keyOutputText or code (@icon/icon_name|@integer/key_code) * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' * character. * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well. * See {@link KeyboardIconsSet} about icon_number. */ public class MoreKeySpecParser { - private static final String TAG = MoreKeySpecParser.class.getSimpleName(); - - private static final char ESCAPE = '\\'; - private static final String LABEL_END = "|"; - private static final String PREFIX_AT = "@"; - private static final String PREFIX_ICON = PREFIX_AT + "icon/"; - private static final String PREFIX_CODE = PREFIX_AT + "integer/"; + private static final char LABEL_END = '|'; + private static final String PREFIX_ICON = Utils.PREFIX_AT + "icon" + Utils.SUFFIX_SLASH; + private static final String PREFIX_CODE = Utils.PREFIX_AT + "integer" + Utils.SUFFIX_SLASH; private MoreKeySpecParser() { // Intentional empty constructor for utility class. @@ -53,8 +49,9 @@ public class MoreKeySpecParser { private static boolean hasIcon(String moreKeySpec) { if (moreKeySpec.startsWith(PREFIX_ICON)) { final int end = indexOfLabelEnd(moreKeySpec, 0); - if (end > 0) + if (end > 0) { return true; + } throw new MoreKeySpecParserError("outputText or code not specified: " + moreKeySpec); } return false; @@ -70,13 +67,14 @@ public class MoreKeySpecParser { } private static String parseEscape(String text) { - if (text.indexOf(ESCAPE) < 0) + if (text.indexOf(Utils.ESCAPE_CHAR) < 0) { return text; + } final int length = text.length(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { final char c = text.charAt(pos); - if (c == ESCAPE && pos + 1 < length) { + if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { sb.append(text.charAt(++pos)); } else { sb.append(c); @@ -86,18 +84,19 @@ public class MoreKeySpecParser { } private static int indexOfLabelEnd(String moreKeySpec, int start) { - if (moreKeySpec.indexOf(ESCAPE, start) < 0) { + if (moreKeySpec.indexOf(Utils.ESCAPE_CHAR, start) < 0) { final int end = moreKeySpec.indexOf(LABEL_END, start); - if (end == 0) + if (end == 0) { throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); + } return end; } final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); - if (c == ESCAPE && pos + 1 < length) { + if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { pos++; - } else if (moreKeySpec.startsWith(LABEL_END, pos)) { + } else if (c == LABEL_END) { return pos; } } @@ -105,79 +104,75 @@ public class MoreKeySpecParser { } public static String getLabel(String moreKeySpec) { - if (hasIcon(moreKeySpec)) + if (hasIcon(moreKeySpec)) { return null; + } final int end = indexOfLabelEnd(moreKeySpec, 0); final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end)) : parseEscape(moreKeySpec); - if (TextUtils.isEmpty(label)) + if (TextUtils.isEmpty(label)) { throw new MoreKeySpecParserError("Empty label: " + moreKeySpec); + } return label; } public static String getOutputText(String moreKeySpec) { - if (hasCode(moreKeySpec)) + if (hasCode(moreKeySpec)) { return null; + } final int end = indexOfLabelEnd(moreKeySpec, 0); if (end > 0) { - if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) + if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); - final String outputText = parseEscape(moreKeySpec.substring(end + LABEL_END.length())); - if (!TextUtils.isEmpty(outputText)) + } + final String outputText = parseEscape( + moreKeySpec.substring(end + /* LABEL_END */1)); + if (!TextUtils.isEmpty(outputText)) { return outputText; + } throw new MoreKeySpecParserError("Empty outputText: " + moreKeySpec); } final String label = getLabel(moreKeySpec); - if (label == null) + if (label == null) { throw new MoreKeySpecParserError("Empty label: " + moreKeySpec); + } // Code is automatically generated for one letter label. See {@link getCode()}. - if (label.length() == 1) - return null; - return label; + return (label.length() == 1) ? null : label; } public static int getCode(Resources res, String moreKeySpec) { if (hasCode(moreKeySpec)) { final int end = indexOfLabelEnd(moreKeySpec, 0); - if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) + if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); - final int resId = getResourceId(res, - moreKeySpec.substring(end + LABEL_END.length() + PREFIX_AT.length())); + } + final int resId = Utils.getResourceId(res, + moreKeySpec.substring(end + /* LABEL_END */1 + /* PREFIX_AT */1), + R.string.english_ime_name); final int code = res.getInteger(resId); return code; } - if (indexOfLabelEnd(moreKeySpec, 0) > 0) - return Keyboard.CODE_UNSPECIFIED; + if (indexOfLabelEnd(moreKeySpec, 0) > 0) { + return Keyboard.CODE_OUTPUT_TEXT; + } final String label = getLabel(moreKeySpec); // Code is automatically generated for one letter label. - if (label != null && label.length() == 1) + if (label != null && label.length() == 1) { return label.charAt(0); - return Keyboard.CODE_UNSPECIFIED; + } + return Keyboard.CODE_OUTPUT_TEXT; } - public static int getIconId(String moreKeySpec) { + public static int getIconAttrId(String moreKeySpec) { if (hasIcon(moreKeySpec)) { - int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1); - final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end); - try { - return Integer.valueOf(iconId); - } catch (NumberFormatException e) { - Log.w(TAG, "illegal icon id specified: " + iconId); - return KeyboardIconsSet.ICON_UNDEFINED; - } + final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length()); + final String name = moreKeySpec.substring(PREFIX_ICON.length(), end); + return KeyboardIconsSet.getIconAttrId(name); } return KeyboardIconsSet.ICON_UNDEFINED; } - private static int getResourceId(Resources res, String name) { - String packageName = res.getResourcePackageName(R.string.english_ime_name); - int resId = res.getIdentifier(name, null, packageName); - if (resId == 0) - throw new MoreKeySpecParserError("Unknown resource: " + name); - return resId; - } - @SuppressWarnings("serial") public static class MoreKeySpecParserError extends RuntimeException { public MoreKeySpecParserError(String message) { @@ -196,21 +191,19 @@ public class MoreKeySpecParser { } }; - public static CharSequence[] filterOut(Resources res, CharSequence[] moreKeys, - CodeFilter filter) { + public static String[] filterOut(Resources res, String[] moreKeys, CodeFilter filter) { if (moreKeys == null || moreKeys.length < 1) { return null; } - if (moreKeys.length == 1 - && filter.shouldFilterOut(getCode(res, moreKeys[0].toString()))) { + if (moreKeys.length == 1 && filter.shouldFilterOut(getCode(res, moreKeys[0]))) { return null; } - ArrayList<CharSequence> filtered = null; + ArrayList<String> filtered = null; for (int i = 0; i < moreKeys.length; i++) { - final CharSequence moreKeySpec = moreKeys[i]; - if (filter.shouldFilterOut(getCode(res, moreKeySpec.toString()))) { + final String moreKeySpec = moreKeys[i]; + if (filter.shouldFilterOut(getCode(res, moreKeySpec))) { if (filtered == null) { - filtered = new ArrayList<CharSequence>(); + filtered = new ArrayList<String>(); for (int j = 0; j < i; j++) { filtered.add(moreKeys[j]); } @@ -225,6 +218,6 @@ public class MoreKeySpecParser { if (filtered.size() == 0) { return null; } - return filtered.toArray(new CharSequence[filtered.size()]); + return filtered.toArray(new String[filtered.size()]); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index d11aaeb96..94c47bdc9 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1881,6 +1881,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } return; } + // We need to log before we commit, because the word composer will store away the user + // typed word. + LatinImeLogger.logOnManualSuggestion(mWordComposer.getTypedWord().toString(), + suggestion.toString(), index, suggestions.mWords); mExpectingUpdateSelection = true; commitChosenWord(suggestion, WordComposer.COMMIT_TYPE_MANUAL_PICK); // Add the word to the auto dictionary if it's not a known word @@ -1890,10 +1894,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else { addToOnlyBigramDictionary(suggestion, 1); } - // TODO: the following is fishy, because it seems there may be cases where we are not - // composing a word at all. Maybe throw an exception if !mWordComposer.isComposingWord() ? - LatinImeLogger.logOnManualSuggestion(mWordComposer.getTypedWord().toString(), - suggestion.toString(), index, suggestions.mWords); // Follow it with a space if (mInputAttributes.mInsertSpaceOnPickSuggestionManually) { sendMagicSpace(); @@ -1924,8 +1924,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Updating the predictions right away may be slow and feel unresponsive on slower // terminals. On the other hand if we just postUpdateBigramPredictions() it will // take a noticeable delay to update them which may feel uneasy. - } - if (showingAddToDictionaryHint) { + } else { if (mIsUserDictionaryAvailable) { mSuggestionsView.showAddToDictionaryHint( suggestion, mSettingsValues.mHintToSaveText); @@ -1942,9 +1941,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar * Commits the chosen word to the text field and saves it for later retrieval. */ private void commitChosenWord(final CharSequence bestWord, final int commitType) { - final KeyboardSwitcher switcher = mKeyboardSwitcher; - if (!switcher.isKeyboardAvailable()) - return; final InputConnection ic = getCurrentInputConnection(); if (ic != null) { mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators); @@ -2133,12 +2129,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final String wordBeforeCursor = ic.getTextBeforeCursor(cancelLength + 1, 0).subSequence(0, cancelLength) .toString(); - if (!autoCorrectedTo.equals(wordBeforeCursor)) { + if (!TextUtils.equals(autoCorrectedTo, wordBeforeCursor)) { throw new RuntimeException("cancelAutoCorrect check failed: we thought we were " + "reverting \"" + autoCorrectedTo + "\", but before the cursor we found \"" + wordBeforeCursor + "\""); } - if (originallyTypedWord.equals(wordBeforeCursor)) { + if (TextUtils.equals(originallyTypedWord, wordBeforeCursor)) { throw new RuntimeException("cancelAutoCorrect check failed: we wanted to cancel " + "auto correction and revert to \"" + originallyTypedWord + "\" but we found this very string before the cursor"); @@ -2158,12 +2154,22 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // "ic" must not be null private void restartSuggestionsOnManuallyPickedTypedWord(final InputConnection ic) { + // Note: this relies on the last word still being held in the WordComposer, in + // the field for suggestion resuming. + // Note: in the interest of code simplicity, we may want to just call + // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving + // the old WordComposer allows to reuse the actual typed coordinates. + mWordComposer.resumeSuggestionOnKeptWord(); + // We resume suggestion, and then we want to set the composing text to the content + // of the word composer again. But since we just manually picked a word, there is + // no composing text at the moment, so we have to delete the word before we set a + // new composing text. final int restartLength = mWordComposer.size(); if (DEBUG) { final String wordBeforeCursor = ic.getTextBeforeCursor(restartLength + 1, 0).subSequence(0, restartLength) .toString(); - if (!mWordComposer.getTypedWord().equals(wordBeforeCursor)) { + if (!TextUtils.equals(mWordComposer.getTypedWord(), wordBeforeCursor)) { throw new RuntimeException("restartSuggestionsOnManuallyPickedTypedWord " + "check failed: we thought we were reverting \"" + mWordComposer.getTypedWord() @@ -2171,13 +2177,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar + wordBeforeCursor + "\""); } } + // Warning: this +1 takes into account the extra space added by the manual pick process. ic.deleteSurroundingText(restartLength + 1, 0); - - // Note: this relies on the last word still being held in the WordComposer - // Note: in the interest of code simplicity, we may want to just call - // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving - // the old WordComposer allows to reuse the actual typed coordinates. - mWordComposer.resumeSuggestionOnKeptWord(); ic.setComposingText(mWordComposer.getTypedWord(), 1); mHandler.cancelUpdateBigramPredictions(); mHandler.postUpdateSuggestions(); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 8e0cfa122..bc8a1301c 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,14 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; -import com.android.inputmethod.compat.InputTypeCompatUtils; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.define.JniLibName; - import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -41,6 +33,14 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.latin.define.JniLibName; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -62,6 +62,12 @@ public class Utils { private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG_EDIT_DISTANCE = false; + // Constants for resource name parsing. + public static final char ESCAPE_CHAR = '\\'; + public static final char PREFIX_AT = '@'; + public static final char SUFFIX_SLASH = '/'; + private static final String PREFIX_STRING = PREFIX_AT + "string"; + private Utils() { // Intentional empty constructor for utility class. } @@ -793,4 +799,62 @@ public class Utils { LatinImeLogger.logOnAutoCorrectionCancelled(); } } + + public static int getResourceId(Resources res, String name, int packageNameResId) { + String packageName = res.getResourcePackageName(packageNameResId); + int resId = res.getIdentifier(name, null, packageName); + if (resId == 0) { + throw new RuntimeException("Unknown resource: " + name); + } + return resId; + } + + public static String resolveStringResource(String text, Resources res, int packageNameResId) { + final int size = text.length(); + if (size < PREFIX_STRING.length()) { + return text; + } + + StringBuilder sb = null; + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == PREFIX_AT && text.startsWith(PREFIX_STRING, pos)) { + if (sb == null) { + sb = new StringBuilder(text.substring(0, pos)); + } + final int end = Utils.searchResourceNameEnd(text, pos + PREFIX_STRING.length()); + final String resName = text.substring(pos + 1, end); + final int resId = getResourceId(res, resName, packageNameResId); + sb.append(res.getString(resId)); + pos = end - 1; + } else if (c == ESCAPE_CHAR) { + pos++; + if (sb != null) { + sb.append(c); + if (pos < size) { + sb.append(text.charAt(pos)); + } + } + } else if (sb != null) { + sb.append(c); + } + } + return (sb == null) ? text : sb.toString(); + } + + private static int searchResourceNameEnd(String text, int start) { + final int size = text.length(); + if (start >= size || text.charAt(start) != SUFFIX_SLASH) { + throw new RuntimeException("Resource name not specified"); + } + for (int pos = start + 1; pos < size; pos++) { + final char c = text.charAt(pos); + // String resource name should be consisted of [a-z_0-9]. + if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) { + continue; + } + return pos; + } + return size; + } } |