Use Add-To-Dictionary indicator only
With this CL, the previously used commit indicator was reverted. Instead we use the add-to-dictionary indicator only at the moment. This CL also fixes the indicator position in bidi context. BUG: 17335734 Change-Id: I5f7cf173ddc30876e2b01320acaff8ba4265edf6
This commit is contained in:
parent
5b9a1e59bb
commit
5896153f93
10 changed files with 267 additions and 418 deletions
|
@ -19,62 +19,11 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<!-- The delay time in milliseconds from to show the commit indicator -->
|
|
||||||
<integer name="text_decorator_delay_in_milliseconds_to_show_commit_indicator">
|
|
||||||
500
|
|
||||||
</integer>
|
|
||||||
|
|
||||||
<!-- The extra margin in dp around the hit area of the commit/add-to-dictionary indicator -->
|
<!-- The extra margin in dp around the hit area of the commit/add-to-dictionary indicator -->
|
||||||
<integer name="text_decorator_hit_area_margin_in_dp">
|
<integer name="text_decorator_hit_area_margin_in_dp">
|
||||||
4
|
4
|
||||||
</integer>
|
</integer>
|
||||||
|
|
||||||
<!-- If true, the commit/add-to-text indicator will be suppressed when the word isn't going to
|
|
||||||
trigger auto-correction. -->
|
|
||||||
<bool name="text_decorator_only_for_auto_correction">true</bool>
|
|
||||||
|
|
||||||
<!-- If true, the commit/add-to-text indicator will be suppressed when the word is already in
|
|
||||||
the dictionary. -->
|
|
||||||
<bool name="text_decorator_only_for_out_of_vocabulary">false</bool>
|
|
||||||
|
|
||||||
<!-- Background color to be used to highlight the target text when the commit indicator is
|
|
||||||
visible. -->
|
|
||||||
<color name="text_decorator_commit_indicator_text_highlight_color">
|
|
||||||
#B6E2DE
|
|
||||||
</color>
|
|
||||||
|
|
||||||
<!-- Background color of the commit indicator. -->
|
|
||||||
<color name="text_decorator_commit_indicator_background_color">
|
|
||||||
#48B6AC
|
|
||||||
</color>
|
|
||||||
|
|
||||||
<!-- Foreground color of the commit indicator. -->
|
|
||||||
<color name="text_decorator_commit_indicator_foreground_color">
|
|
||||||
#FFFFFF
|
|
||||||
</color>
|
|
||||||
|
|
||||||
<!-- Viewport size of "text_decorator_commit_indicator_path". -->
|
|
||||||
<integer name="text_decorator_commit_indicator_path_size">
|
|
||||||
480
|
|
||||||
</integer>
|
|
||||||
|
|
||||||
<!-- Coordinates of the closed path to be used to render the commit indicator.
|
|
||||||
The format is: X[0], Y[0], X[1], Y[1], ..., X[N-1], Y[N-1] -->
|
|
||||||
<integer-array name="text_decorator_commit_indicator_path">
|
|
||||||
<item>180</item>
|
|
||||||
<item>323</item>
|
|
||||||
<item>97</item>
|
|
||||||
<item>240</item>
|
|
||||||
<item>68</item>
|
|
||||||
<item>268</item>
|
|
||||||
<item>180</item>
|
|
||||||
<item>380</item>
|
|
||||||
<item>420</item>
|
|
||||||
<item>140</item>
|
|
||||||
<item>392</item>
|
|
||||||
<item>112</item>
|
|
||||||
</integer-array>
|
|
||||||
|
|
||||||
<!-- Background color to be used to highlight the target text when the add-to-dictionary
|
<!-- Background color to be used to highlight the target text when the add-to-dictionary
|
||||||
indicator is visible. -->
|
indicator is visible. -->
|
||||||
<color name="text_decorator_add_to_dictionary_indicator_text_highlight_color">
|
<color name="text_decorator_add_to_dictionary_indicator_text_highlight_color">
|
||||||
|
|
|
@ -41,6 +41,8 @@ public final class CursorAnchorInfoCompatWrapper {
|
||||||
|
|
||||||
// Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX).
|
// Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX).
|
||||||
private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass;
|
private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass;
|
||||||
|
private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod;
|
||||||
|
private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod;
|
||||||
private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod;
|
private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod;
|
||||||
private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod;
|
private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod;
|
||||||
private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod;
|
private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod;
|
||||||
|
@ -52,10 +54,14 @@ public final class CursorAnchorInfoCompatWrapper {
|
||||||
private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod;
|
private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod;
|
||||||
private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod;
|
private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod;
|
||||||
|
|
||||||
private static int COMPOSING_TEXT_START_DEFAULT = -1;
|
private static int INVALID_TEXT_INDEX = -1;
|
||||||
static {
|
static {
|
||||||
sCursorAnchorInfoClass = CompatUtils.getClassWrapper(
|
sCursorAnchorInfoClass = CompatUtils.getClassWrapper(
|
||||||
"android.view.inputmethod.CursorAnchorInfo");
|
"android.view.inputmethod.CursorAnchorInfo");
|
||||||
|
sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
|
"getSelectionStart", INVALID_TEXT_INDEX);
|
||||||
|
sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
|
"getSelectionEnd", INVALID_TEXT_INDEX);
|
||||||
sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod(
|
sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod(
|
||||||
"getCharacterBounds", (RectF)null, int.class);
|
"getCharacterBounds", (RectF)null, int.class);
|
||||||
sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
|
@ -63,7 +69,7 @@ public final class CursorAnchorInfoCompatWrapper {
|
||||||
sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod(
|
sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod(
|
||||||
"getComposingText", (CharSequence)null);
|
"getComposingText", (CharSequence)null);
|
||||||
sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
"getComposingTextStart", COMPOSING_TEXT_START_DEFAULT);
|
"getComposingTextStart", INVALID_TEXT_INDEX);
|
||||||
sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
"getInsertionMarkerBaseline", 0.0f);
|
"getInsertionMarkerBaseline", 0.0f);
|
||||||
sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
|
||||||
|
@ -105,6 +111,14 @@ public final class CursorAnchorInfoCompatWrapper {
|
||||||
return FakeHolder.sInstance;
|
return FakeHolder.sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSelectionStart() {
|
||||||
|
return sGetSelectionStartMethod.invoke(mInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectionEnd() {
|
||||||
|
return sGetSelectionEndMethod.invoke(mInstance);
|
||||||
|
}
|
||||||
|
|
||||||
public CharSequence getComposingText() {
|
public CharSequence getComposingText() {
|
||||||
return sGetComposingTextMethod.invoke(mInstance);
|
return sGetComposingTextMethod.invoke(mInstance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,22 @@
|
||||||
package com.android.inputmethod.keyboard;
|
package com.android.inputmethod.keyboard;
|
||||||
|
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.inputmethodservice.InputMethodService;
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.CursorAnchorInfo;
|
||||||
|
|
||||||
import com.android.inputmethod.annotations.UsedForTesting;
|
import com.android.inputmethod.annotations.UsedForTesting;
|
||||||
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
|
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
|
||||||
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
|
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A controller class of commit/add-to-dictionary indicator (a.k.a. TextDecorator). This class
|
* A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class
|
||||||
* is designed to be independent of UI subsystems such as {@link View}. All the UI related
|
* is designed to be independent of UI subsystems such as {@link View}. All the UI related
|
||||||
* operations are delegated to {@link TextDecoratorUi} via {@link TextDecoratorUiOperator}.
|
* operations are delegated to {@link TextDecoratorUi} via {@link TextDecoratorUiOperator}.
|
||||||
*/
|
*/
|
||||||
|
@ -41,18 +40,22 @@ public class TextDecorator {
|
||||||
private static final String TAG = TextDecorator.class.getSimpleName();
|
private static final String TAG = TextDecorator.class.getSimpleName();
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
private static final int MODE_NONE = 0;
|
private static final int INVALID_CURSOR_INDEX = -1;
|
||||||
private static final int MODE_COMMIT = 1;
|
|
||||||
private static final int MODE_ADD_TO_DICTIONARY = 2;
|
|
||||||
|
|
||||||
private int mMode = MODE_NONE;
|
private static final int MODE_MONITOR = 0;
|
||||||
|
private static final int MODE_WAITING_CURSOR_INDEX = 1;
|
||||||
|
private static final int MODE_SHOWING_INDICATOR = 2;
|
||||||
|
|
||||||
private final PointF mLocalOrigin = new PointF();
|
private int mMode = MODE_MONITOR;
|
||||||
private final RectF mRelativeIndicatorBounds = new RectF();
|
|
||||||
private final RectF mRelativeComposingTextBounds = new RectF();
|
private String mLastComposingText = null;
|
||||||
|
private RectF mIndicatorBoundsForLastComposingText = new RectF();
|
||||||
|
private RectF mComposingTextBoundsForLastComposingText = new RectF();
|
||||||
|
|
||||||
private boolean mIsFullScreenMode = false;
|
private boolean mIsFullScreenMode = false;
|
||||||
private SuggestedWordInfo mWaitingWord = null;
|
private String mWaitingWord = null;
|
||||||
|
private int mWaitingCursorStart = INVALID_CURSOR_INDEX;
|
||||||
|
private int mWaitingCursorEnd = INVALID_CURSOR_INDEX;
|
||||||
private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;
|
private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -63,16 +66,10 @@ public class TextDecorator {
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
/**
|
/**
|
||||||
* Called when the user clicks the composing text to commit.
|
* Called when the user clicks the indicator to add the word into the dictionary.
|
||||||
* @param wordInfo the suggested word which the user clicked on.
|
* @param word the word which the user clicked on.
|
||||||
*/
|
*/
|
||||||
void onClickComposingTextToCommit(final SuggestedWordInfo wordInfo);
|
void onClickComposingTextToAddToDictionary(final String word);
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user clicks the composing text to add the word into the dictionary.
|
|
||||||
* @param wordInfo the suggested word which the user clicked on.
|
|
||||||
*/
|
|
||||||
void onClickComposingTextToAddToDictionary(final SuggestedWordInfo wordInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextDecorator(final Listener listener) {
|
public TextDecorator(final Listener listener) {
|
||||||
|
@ -103,46 +100,19 @@ public class TextDecorator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the "Commit" indicator and associates it with the given suggested word.
|
* Shows the "Add to dictionary" indicator and associates it with associating the given word.
|
||||||
*
|
*
|
||||||
* <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and
|
* @param word the word which should be associated with the indicator. This object will be
|
||||||
* {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call
|
* passed back in {@link Listener#onClickComposingTextToAddToDictionary(String)}.
|
||||||
* {@link #reset()} to hide the indicator.</p>
|
* @param selectionStart the cursor index (inclusive) when the indicator should be displayed.
|
||||||
*
|
* @param selectionEnd the cursor index (exclusive) when the indicator should be displayed.
|
||||||
* @param wordInfo the suggested word which should be associated with the indicator. This object
|
|
||||||
* will be passed back in {@link Listener#onClickComposingTextToCommit(SuggestedWordInfo)}
|
|
||||||
*/
|
*/
|
||||||
public void showCommitIndicator(final SuggestedWordInfo wordInfo) {
|
public void showAddToDictionaryIndicator(final String word, final int selectionStart,
|
||||||
if (mMode == MODE_COMMIT && wordInfo != null &&
|
final int selectionEnd) {
|
||||||
TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) {
|
mWaitingWord = word;
|
||||||
// Skip layout for better performance.
|
mWaitingCursorStart = selectionStart;
|
||||||
return;
|
mWaitingCursorEnd = selectionEnd;
|
||||||
}
|
mMode = MODE_WAITING_CURSOR_INDEX;
|
||||||
mWaitingWord = wordInfo;
|
|
||||||
mMode = MODE_COMMIT;
|
|
||||||
layoutLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the "Add to dictionary" indicator and associates it with associating the given
|
|
||||||
* suggested word.
|
|
||||||
*
|
|
||||||
* <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and
|
|
||||||
* {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call
|
|
||||||
* {@link #reset()} to hide the indicator.</p>
|
|
||||||
*
|
|
||||||
* @param wordInfo the suggested word which should be associated with the indicator. This object
|
|
||||||
* will be passed back in
|
|
||||||
* {@link Listener#onClickComposingTextToAddToDictionary(SuggestedWordInfo)}.
|
|
||||||
*/
|
|
||||||
public void showAddToDictionaryIndicator(final SuggestedWordInfo wordInfo) {
|
|
||||||
if (mMode == MODE_ADD_TO_DICTIONARY && wordInfo != null &&
|
|
||||||
TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) {
|
|
||||||
// Skip layout for better performance.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mWaitingWord = wordInfo;
|
|
||||||
mMode = MODE_ADD_TO_DICTIONARY;
|
|
||||||
layoutLater();
|
layoutLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -165,18 +135,19 @@ public class TextDecorator {
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
mWaitingWord = null;
|
mWaitingWord = null;
|
||||||
mMode = MODE_NONE;
|
mMode = MODE_MONITOR;
|
||||||
mLocalOrigin.set(0.0f, 0.0f);
|
mWaitingCursorStart = INVALID_CURSOR_INDEX;
|
||||||
mRelativeIndicatorBounds.set(0.0f, 0.0f, 0.0f, 0.0f);
|
mWaitingCursorEnd = INVALID_CURSOR_INDEX;
|
||||||
mRelativeComposingTextBounds.set(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
cancelLayoutInternalExpectedly("Resetting internal state.");
|
cancelLayoutInternalExpectedly("Resetting internal state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo()} is called.
|
* Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}
|
||||||
|
* is called.
|
||||||
*
|
*
|
||||||
* <p>CAVEAT: Currently the input method author is responsible for ignoring
|
* <p>CAVEAT: Currently the input method author is responsible for ignoring
|
||||||
* {@link InputMethodService#onUpdateCursorAnchorInfo()} called in full screen mode.</p>
|
* {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} called in full screen
|
||||||
|
* mode.</p>
|
||||||
* @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
|
* @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
|
||||||
*/
|
*/
|
||||||
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
||||||
|
@ -185,29 +156,6 @@ public class TextDecorator {
|
||||||
layoutImmediately();
|
layoutImmediately();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides indicator if the new composing text doesn't match the expected one.
|
|
||||||
*
|
|
||||||
* <p>Calling this method is optional but recommended whenever the new composition is passed to
|
|
||||||
* the application. The motivation of this method is to reduce the UI latency. With this method,
|
|
||||||
* we can hide the indicator without waiting the arrival of the
|
|
||||||
* {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} callback, assuming that
|
|
||||||
* the application accepts the new composing text without any modification. Even if this
|
|
||||||
* assumption is false, the indicator will be shown again when
|
|
||||||
* {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is actually received.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param newComposingText the new composing text that is being passed to the application.
|
|
||||||
*/
|
|
||||||
public void hideIndicatorIfNecessary(final CharSequence newComposingText) {
|
|
||||||
if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!TextUtils.equals(newComposingText, mWaitingWord.mWord)) {
|
|
||||||
mUiOperator.hideUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelLayoutInternalUnexpectedly(final String message) {
|
private void cancelLayoutInternalUnexpectedly(final String message) {
|
||||||
mUiOperator.hideUi();
|
mUiOperator.hideUi();
|
||||||
Log.d(TAG, message);
|
Log.d(TAG, message);
|
||||||
|
@ -232,15 +180,6 @@ public class TextDecorator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void layoutMain() {
|
private void layoutMain() {
|
||||||
if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
|
|
||||||
if (mMode == MODE_NONE) {
|
|
||||||
cancelLayoutInternalExpectedly("Not ready for layouting.");
|
|
||||||
} else {
|
|
||||||
cancelLayoutInternalUnexpectedly("Unknown mMode=" + mMode);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;
|
final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;
|
||||||
|
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
|
@ -254,104 +193,117 @@ public class TextDecorator {
|
||||||
}
|
}
|
||||||
|
|
||||||
final CharSequence composingText = info.getComposingText();
|
final CharSequence composingText = info.getComposingText();
|
||||||
if (mMode == MODE_COMMIT) {
|
if (!TextUtils.isEmpty(composingText)) {
|
||||||
if (composingText == null) {
|
|
||||||
cancelLayoutInternalExpectedly("composingText is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int composingTextStart = info.getComposingTextStart();
|
final int composingTextStart = info.getComposingTextStart();
|
||||||
final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
|
final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
|
||||||
final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex);
|
final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex);
|
||||||
final int lastCharRectFlag = info.getCharacterBoundsFlags(lastCharRectIndex);
|
final int lastCharRectFlags = info.getCharacterBoundsFlags(lastCharRectIndex);
|
||||||
final boolean hasInvisibleRegionInLastCharRect =
|
final boolean hasInvisibleRegionInLastCharRect =
|
||||||
(lastCharRectFlag & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
|
(lastCharRectFlags & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
|
||||||
!= 0;
|
!= 0;
|
||||||
if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) {
|
if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) {
|
||||||
mUiOperator.hideUi();
|
mUiOperator.hideUi();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RectF segmentStartCharRect = new RectF(lastCharRect);
|
|
||||||
for (int i = composingText.length() - 2; i >= 0; --i) {
|
// Note that the following layout information is fragile, and must be invalidated
|
||||||
final RectF charRect = info.getCharacterBounds(composingTextStart + i);
|
// even when surrounding text next to the composing text is changed because it can
|
||||||
if (charRect == null) {
|
// affect how the composing text is rendered.
|
||||||
|
// TODO: Investigate if we can change the input logic to make the target text
|
||||||
|
// composing state so that we can retrieve the character bounds reliably.
|
||||||
|
final String composingTextString = composingText.toString();
|
||||||
|
final float top = lastCharRect.top;
|
||||||
|
final float bottom = lastCharRect.bottom;
|
||||||
|
float left = lastCharRect.left;
|
||||||
|
float right = lastCharRect.right;
|
||||||
|
boolean useRtlLayout = false;
|
||||||
|
for (int i = composingText.length() - 1; i >= 0; --i) {
|
||||||
|
final int characterIndex = composingTextStart + i;
|
||||||
|
final RectF characterBounds = info.getCharacterBounds(characterIndex);
|
||||||
|
final int characterBoundsFlags = info.getCharacterBoundsFlags(characterIndex);
|
||||||
|
if (characterBounds == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (charRect.top != segmentStartCharRect.top) {
|
if (characterBounds.top != top) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (charRect.bottom != segmentStartCharRect.bottom) {
|
if (characterBounds.bottom != bottom) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
segmentStartCharRect.set(charRect);
|
if ((characterBoundsFlags & CursorAnchorInfoCompatWrapper.FLAG_IS_RTL) != 0) {
|
||||||
|
// This is for both RTL text and bi-directional text. RTL languages usually mix
|
||||||
|
// RTL characters with LTR characters and in this case we should display the
|
||||||
|
// indicator on the left, while in LTR languages that normally never happens.
|
||||||
|
// TODO: Try to come up with a better algorithm.
|
||||||
|
useRtlLayout = true;
|
||||||
|
}
|
||||||
|
left = Math.min(characterBounds.left, left);
|
||||||
|
right = Math.max(characterBounds.right, right);
|
||||||
}
|
}
|
||||||
|
mLastComposingText = composingTextString;
|
||||||
mLocalOrigin.set(lastCharRect.right, lastCharRect.top);
|
mComposingTextBoundsForLastComposingText.set(left, top, right, bottom);
|
||||||
mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top,
|
// The height and width of the indicator is the same as the height of the composing
|
||||||
lastCharRect.right + lastCharRect.height(), lastCharRect.bottom);
|
// text.
|
||||||
mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
final float indicatorSize = bottom - top;
|
||||||
|
mIndicatorBoundsForLastComposingText.set(0.0f, 0.0f, indicatorSize, indicatorSize);
|
||||||
mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top,
|
// The horizontal position of the indicator depends on the text direction.
|
||||||
lastCharRect.right + lastCharRect.height(), lastCharRect.bottom);
|
final float indicatorTop = top;
|
||||||
mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
final float indicatorLeft;
|
||||||
|
if (useRtlLayout) {
|
||||||
mRelativeComposingTextBounds.set(segmentStartCharRect.left, segmentStartCharRect.top,
|
indicatorLeft = left - indicatorSize;
|
||||||
segmentStartCharRect.right, segmentStartCharRect.bottom);
|
} else {
|
||||||
mRelativeComposingTextBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
indicatorLeft = right;
|
||||||
|
|
||||||
if (mWaitingWord == null) {
|
|
||||||
cancelLayoutInternalExpectedly("mWaitingText is null.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(mWaitingWord.mWord)) {
|
mIndicatorBoundsForLastComposingText.offset(indicatorLeft, indicatorTop);
|
||||||
cancelLayoutInternalExpectedly("mWaitingText.mWord is empty.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!TextUtils.equals(composingText, mWaitingWord.mWord)) {
|
|
||||||
// This is indeed an expected situation because of the asynchronous nature of
|
|
||||||
// input method framework in Android. Note that composingText is notified from the
|
|
||||||
// application, while mWaitingWord.mWord is obtained directly from the InputLogic.
|
|
||||||
cancelLayoutInternalExpectedly(
|
|
||||||
"Composing text doesn't match the one we are waiting for.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!mIsFullScreenMode && !TextUtils.isEmpty(composingText)) {
|
|
||||||
// This is an unexpected case.
|
|
||||||
// TODO: Document this.
|
|
||||||
mUiOperator.hideUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// In MODE_ADD_TO_DICTIONARY, we cannot retrieve the character position at all because
|
|
||||||
// of the lack of composing text. We will use the insertion marker position instead.
|
|
||||||
if ((info.getInsertionMarkerFlags() &
|
|
||||||
CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) {
|
|
||||||
mUiOperator.hideUi();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final float insertionMarkerHolizontal = info.getInsertionMarkerHorizontal();
|
|
||||||
final float insertionMarkerTop = info.getInsertionMarkerTop();
|
|
||||||
mLocalOrigin.set(insertionMarkerHolizontal, insertionMarkerTop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final RectF indicatorBounds = new RectF(mRelativeIndicatorBounds);
|
final int selectionStart = info.getSelectionStart();
|
||||||
final RectF composingTextBounds = new RectF(mRelativeComposingTextBounds);
|
final int selectionEnd = info.getSelectionEnd();
|
||||||
indicatorBounds.offset(mLocalOrigin.x, mLocalOrigin.y);
|
switch (mMode) {
|
||||||
composingTextBounds.offset(mLocalOrigin.x, mLocalOrigin.y);
|
case MODE_MONITOR:
|
||||||
mUiOperator.layoutUi(mMode == MODE_COMMIT, matrix, indicatorBounds, composingTextBounds);
|
mUiOperator.hideUi();
|
||||||
|
return;
|
||||||
|
case MODE_WAITING_CURSOR_INDEX:
|
||||||
|
if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
|
||||||
|
mUiOperator.hideUi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMode = MODE_SHOWING_INDICATOR;
|
||||||
|
break;
|
||||||
|
case MODE_SHOWING_INDICATOR:
|
||||||
|
if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
|
||||||
|
mUiOperator.hideUi();
|
||||||
|
mMode = MODE_MONITOR;
|
||||||
|
mWaitingCursorStart = INVALID_CURSOR_INDEX;
|
||||||
|
mWaitingCursorEnd = INVALID_CURSOR_INDEX;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cancelLayoutInternalUnexpectedly("Unexpected internal mode=" + mMode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.equals(mLastComposingText, mWaitingWord)) {
|
||||||
|
cancelLayoutInternalUnexpectedly("mLastComposingText doesn't match mWaitingWord");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((info.getInsertionMarkerFlags() &
|
||||||
|
CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) {
|
||||||
|
mUiOperator.hideUi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mUiOperator.layoutUi(matrix, mIndicatorBoundsForLastComposingText,
|
||||||
|
mComposingTextBoundsForLastComposingText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClickIndicator() {
|
private void onClickIndicator() {
|
||||||
if (mWaitingWord == null || TextUtils.isEmpty(mWaitingWord.mWord)) {
|
if (mMode != MODE_SHOWING_INDICATOR) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (mMode) {
|
mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
|
||||||
case MODE_COMMIT:
|
|
||||||
mListener.onClickComposingTextToCommit(mWaitingWord);
|
|
||||||
break;
|
|
||||||
case MODE_ADD_TO_DICTIONARY:
|
|
||||||
mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this);
|
private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this);
|
||||||
|
@ -407,10 +359,7 @@ public class TextDecorator {
|
||||||
|
|
||||||
private final static Listener EMPTY_LISTENER = new Listener() {
|
private final static Listener EMPTY_LISTENER = new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) {
|
public void onClickComposingTextToAddToDictionary(final String word) {
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -425,8 +374,7 @@ public class TextDecorator {
|
||||||
public void setOnClickListener(Runnable listener) {
|
public void setOnClickListener(Runnable listener) {
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void layoutUi(boolean isCommitMode, Matrix matrix, RectF indicatorBounds,
|
public void layoutUi(Matrix matrix, RectF indicatorBounds, RectF composingTextBounds) {
|
||||||
RectF composingTextBounds) {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000;
|
private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000;
|
||||||
|
|
||||||
private final RelativeLayout mLocalRootView;
|
private final RelativeLayout mLocalRootView;
|
||||||
private final CommitIndicatorView mCommitIndicatorView;
|
|
||||||
private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView;
|
private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView;
|
||||||
private final PopupWindow mTouchEventWindow;
|
private final PopupWindow mTouchEventWindow;
|
||||||
private final View mTouchEventWindowClickListenerView;
|
private final View mTouchEventWindowClickListenerView;
|
||||||
|
@ -73,9 +72,7 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
|
||||||
final ViewGroup contentView = getContentView(inputView);
|
final ViewGroup contentView = getContentView(inputView);
|
||||||
mCommitIndicatorView = new CommitIndicatorView(context);
|
|
||||||
mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context);
|
mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context);
|
||||||
mLocalRootView.addView(mCommitIndicatorView);
|
|
||||||
mLocalRootView.addView(mAddToDictionaryIndicatorView);
|
mLocalRootView.addView(mAddToDictionaryIndicatorView);
|
||||||
if (contentView != null) {
|
if (contentView != null) {
|
||||||
contentView.addView(mLocalRootView);
|
contentView.addView(mLocalRootView);
|
||||||
|
@ -110,17 +107,15 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideUi() {
|
public void hideUi() {
|
||||||
mCommitIndicatorView.setVisibility(View.GONE);
|
|
||||||
mAddToDictionaryIndicatorView.setVisibility(View.GONE);
|
mAddToDictionaryIndicatorView.setVisibility(View.GONE);
|
||||||
mTouchEventWindow.dismiss();
|
mTouchEventWindow.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void layoutUi(final boolean isCommitMode, final Matrix matrix,
|
public void layoutUi(final Matrix matrix, final RectF indicatorBounds,
|
||||||
final RectF indicatorBounds, final RectF composingTextBounds) {
|
final RectF composingTextBounds) {
|
||||||
final RectF indicatorBoundsInScreenCoordinates = new RectF();
|
final RectF indicatorBoundsInScreenCoordinates = new RectF();
|
||||||
matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds);
|
matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds);
|
||||||
mCommitIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
|
|
||||||
mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
|
mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
|
||||||
|
|
||||||
final RectF hitAreaBounds = new RectF(composingTextBounds);
|
final RectF hitAreaBounds = new RectF(composingTextBounds);
|
||||||
|
@ -133,20 +128,9 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
mLocalRootView.getLocationOnScreen(originScreen);
|
mLocalRootView.getLocationOnScreen(originScreen);
|
||||||
final int viewOriginX = originScreen[0];
|
final int viewOriginX = originScreen[0];
|
||||||
final int viewOriginY = originScreen[1];
|
final int viewOriginY = originScreen[1];
|
||||||
|
mAddToDictionaryIndicatorView.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX);
|
||||||
final View toBeShown;
|
mAddToDictionaryIndicatorView.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY);
|
||||||
final View toBeHidden;
|
mAddToDictionaryIndicatorView.setVisibility(View.VISIBLE);
|
||||||
if (isCommitMode) {
|
|
||||||
toBeShown = mCommitIndicatorView;
|
|
||||||
toBeHidden = mAddToDictionaryIndicatorView;
|
|
||||||
} else {
|
|
||||||
toBeShown = mAddToDictionaryIndicatorView;
|
|
||||||
toBeHidden = mCommitIndicatorView;
|
|
||||||
}
|
|
||||||
toBeShown.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX);
|
|
||||||
toBeShown.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY);
|
|
||||||
toBeShown.setVisibility(View.VISIBLE);
|
|
||||||
toBeHidden.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (mTouchEventWindow.isShowing()) {
|
if (mTouchEventWindow.isShowing()) {
|
||||||
mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
|
mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
|
||||||
|
@ -239,15 +223,6 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
return windowContentView;
|
return windowContentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class CommitIndicatorView extends TextDecoratorUi.IndicatorView {
|
|
||||||
public CommitIndicatorView(final Context context) {
|
|
||||||
super(context, R.array.text_decorator_commit_indicator_path,
|
|
||||||
R.integer.text_decorator_commit_indicator_path_size,
|
|
||||||
R.color.text_decorator_commit_indicator_background_color,
|
|
||||||
R.color.text_decorator_commit_indicator_foreground_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView {
|
private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView {
|
||||||
public AddToDictionaryIndicatorView(final Context context) {
|
public AddToDictionaryIndicatorView(final Context context) {
|
||||||
super(context, R.array.text_decorator_add_to_dictionary_indicator_path,
|
super(context, R.array.text_decorator_add_to_dictionary_indicator_path,
|
||||||
|
|
|
@ -44,12 +44,10 @@ public interface TextDecoratorUiOperator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the layout should be updated.
|
* Called when the layout should be updated.
|
||||||
* @param isCommitMode {@code true} if the commit indicator should be shown. Show the
|
|
||||||
* add-to-dictionary indicator otherwise.
|
|
||||||
* @param matrix The matrix that transforms the local coordinates into the screen coordinates.
|
* @param matrix The matrix that transforms the local coordinates into the screen coordinates.
|
||||||
* @param indicatorBounds The bounding box of the indicator, in local coordinates.
|
* @param indicatorBounds The bounding box of the indicator, in local coordinates.
|
||||||
* @param composingTextBounds The bounding box of the composing text, in local coordinates.
|
* @param composingTextBounds The bounding box of the composing text, in local coordinates.
|
||||||
*/
|
*/
|
||||||
void layoutUi(final boolean isCommitMode, final Matrix matrix, final RectF indicatorBounds,
|
void layoutUi(final Matrix matrix, final RectF indicatorBounds,
|
||||||
final RectF composingTextBounds);
|
final RectF composingTextBounds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,9 +188,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
|
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
|
||||||
private static final int MSG_RESET_CACHES = 7;
|
private static final int MSG_RESET_CACHES = 7;
|
||||||
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
|
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
|
||||||
private static final int MSG_SHOW_COMMIT_INDICATOR = 9;
|
|
||||||
// Update this when adding new messages
|
// Update this when adding new messages
|
||||||
private static final int MSG_LAST = MSG_SHOW_COMMIT_INDICATOR;
|
private static final int MSG_LAST = MSG_WAIT_FOR_DICTIONARY_LOAD;
|
||||||
|
|
||||||
private static final int ARG1_NOT_GESTURE_INPUT = 0;
|
private static final int ARG1_NOT_GESTURE_INPUT = 0;
|
||||||
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
|
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
|
||||||
|
@ -201,7 +200,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
private int mDelayInMillisecondsToUpdateSuggestions;
|
private int mDelayInMillisecondsToUpdateSuggestions;
|
||||||
private int mDelayInMillisecondsToUpdateShiftState;
|
private int mDelayInMillisecondsToUpdateShiftState;
|
||||||
private int mDelayInMillisecondsToShowCommitIndicator;
|
|
||||||
|
|
||||||
public UIHandler(final LatinIME ownerInstance) {
|
public UIHandler(final LatinIME ownerInstance) {
|
||||||
super(ownerInstance);
|
super(ownerInstance);
|
||||||
|
@ -217,8 +215,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
R.integer.config_delay_in_milliseconds_to_update_suggestions);
|
R.integer.config_delay_in_milliseconds_to_update_suggestions);
|
||||||
mDelayInMillisecondsToUpdateShiftState = res.getInteger(
|
mDelayInMillisecondsToUpdateShiftState = res.getInteger(
|
||||||
R.integer.config_delay_in_milliseconds_to_update_shift_state);
|
R.integer.config_delay_in_milliseconds_to_update_shift_state);
|
||||||
mDelayInMillisecondsToShowCommitIndicator = res.getInteger(
|
|
||||||
R.integer.text_decorator_delay_in_milliseconds_to_show_commit_indicator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -276,14 +272,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
latinIme.getCurrentRecapitalizeState());
|
latinIme.getCurrentRecapitalizeState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_SHOW_COMMIT_INDICATOR:
|
|
||||||
// Protocol of MSG_SET_COMMIT_INDICATOR_ENABLED:
|
|
||||||
// - what: MSG_SHOW_COMMIT_INDICATOR
|
|
||||||
// - arg1: not used.
|
|
||||||
// - arg2: not used.
|
|
||||||
// - obj: the Runnable object to be called back.
|
|
||||||
((Runnable) msg.obj).run();
|
|
||||||
break;
|
|
||||||
case MSG_WAIT_FOR_DICTIONARY_LOAD:
|
case MSG_WAIT_FOR_DICTIONARY_LOAD:
|
||||||
Log.i(TAG, "Timeout waiting for dictionary load");
|
Log.i(TAG, "Timeout waiting for dictionary load");
|
||||||
break;
|
break;
|
||||||
|
@ -384,19 +372,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
|
obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Posts a delayed task to show the commit indicator.
|
|
||||||
*
|
|
||||||
* <p>Only one task can exist in the queue. When this method is called, any prior task that
|
|
||||||
* has not yet fired will be canceled.</p>
|
|
||||||
* @param task the runnable object that will be fired when the delayed task is dispatched.
|
|
||||||
*/
|
|
||||||
public void postShowCommitIndicatorTask(final Runnable task) {
|
|
||||||
removeMessages(MSG_SHOW_COMMIT_INDICATOR);
|
|
||||||
sendMessageDelayed(obtainMessage(MSG_SHOW_COMMIT_INDICATOR, task),
|
|
||||||
mDelayInMillisecondsToShowCommitIndicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Working variables for the following methods.
|
// Working variables for the following methods.
|
||||||
private boolean mIsOrientationChanging;
|
private boolean mIsOrientationChanging;
|
||||||
private boolean mPendingSuccessiveImsCallback;
|
private boolean mPendingSuccessiveImsCallback;
|
||||||
|
|
|
@ -252,7 +252,7 @@ public final class RichInputConnection {
|
||||||
* See {@link InputConnection#commitText(CharSequence, int)}.
|
* See {@link InputConnection#commitText(CharSequence, int)}.
|
||||||
*/
|
*/
|
||||||
public void commitText(final CharSequence text, final int newCursorPosition) {
|
public void commitText(final CharSequence text, final int newCursorPosition) {
|
||||||
commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT);
|
commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT, text.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,9 +265,11 @@ public final class RichInputConnection {
|
||||||
* the background color. Note that this method specifies {@link BackgroundColorSpan} with
|
* the background color. Note that this method specifies {@link BackgroundColorSpan} with
|
||||||
* {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until
|
* {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until
|
||||||
* {@link #finishComposingText()} is called.
|
* {@link #finishComposingText()} is called.
|
||||||
|
* @param coloredTextLength the length of text, in Java chars, which should be rendered with
|
||||||
|
* the given background color.
|
||||||
*/
|
*/
|
||||||
public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition,
|
public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition,
|
||||||
final int color) {
|
final int color, final int coloredTextLength) {
|
||||||
if (DEBUG_BATCH_NESTING) checkBatchEdit();
|
if (DEBUG_BATCH_NESTING) checkBatchEdit();
|
||||||
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
|
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
|
||||||
mCommittedTextBeforeComposingText.append(text);
|
mCommittedTextBeforeComposingText.append(text);
|
||||||
|
@ -285,7 +287,8 @@ public final class RichInputConnection {
|
||||||
mTempObjectForCommitText.clear();
|
mTempObjectForCommitText.clear();
|
||||||
mTempObjectForCommitText.append(text);
|
mTempObjectForCommitText.append(text);
|
||||||
final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color);
|
final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color);
|
||||||
mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, text.length(),
|
final int spanLength = Math.min(coloredTextLength, text.length());
|
||||||
|
mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, spanLength,
|
||||||
Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
mIC.commitText(mTempObjectForCommitText, newCursorPosition);
|
mIC.commitText(mTempObjectForCommitText, newCursorPosition);
|
||||||
mLastCommittedTextHasBackgroundColor = true;
|
mLastCommittedTextHasBackgroundColor = true;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import android.util.Log;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.inputmethod.CorrectionInfo;
|
import android.view.inputmethod.CorrectionInfo;
|
||||||
|
import android.view.inputmethod.CursorAnchorInfo;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
|
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
|
||||||
|
@ -90,12 +91,8 @@ public final class InputLogic {
|
||||||
|
|
||||||
private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() {
|
private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) {
|
public void onClickComposingTextToAddToDictionary(final String word) {
|
||||||
mLatinIME.pickSuggestionManually(wordInfo);
|
mLatinIME.addWordToUserDictionary(word);
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) {
|
|
||||||
mLatinIME.addWordToUserDictionary(wordInfo.mWord);
|
|
||||||
mLatinIME.dismissAddToDictionaryHint();
|
mLatinIME.dismissAddToDictionaryHint();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -170,6 +167,7 @@ public final class InputLogic {
|
||||||
mConnection.requestCursorUpdates(true /* enableMonitor */,
|
mConnection.requestCursorUpdates(true /* enableMonitor */,
|
||||||
true /* requestImmediateCallback */);
|
true /* requestImmediateCallback */);
|
||||||
}
|
}
|
||||||
|
mTextDecorator.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,17 +331,8 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo);
|
final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo);
|
||||||
final boolean shouldShowAddToDictionaryIndicator =
|
commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
|
||||||
shouldShowAddToDictionaryHint && settingsValues.mShouldShowUiToAcceptTypedWord;
|
LastComposedWord.NOT_A_SEPARATOR);
|
||||||
final int backgroundColor;
|
|
||||||
if (shouldShowAddToDictionaryIndicator) {
|
|
||||||
backgroundColor = settingsValues.mTextHighlightColorForAddToDictionaryIndicator;
|
|
||||||
} else {
|
|
||||||
backgroundColor = Color.TRANSPARENT;
|
|
||||||
}
|
|
||||||
commitChosenWordWithBackgroundColor(settingsValues, suggestion,
|
|
||||||
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR,
|
|
||||||
backgroundColor);
|
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
// Don't allow cancellation of manual pick
|
// Don't allow cancellation of manual pick
|
||||||
mLastComposedWord.deactivate();
|
mLastComposedWord.deactivate();
|
||||||
|
@ -358,9 +347,6 @@ public final class InputLogic {
|
||||||
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
|
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
|
||||||
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
|
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
|
||||||
}
|
}
|
||||||
if (shouldShowAddToDictionaryIndicator) {
|
|
||||||
mTextDecorator.showAddToDictionaryIndicator(suggestionInfo);
|
|
||||||
}
|
|
||||||
return inputTransaction;
|
return inputTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,6 +416,9 @@ public final class InputLogic {
|
||||||
mRecapitalizeStatus.enable();
|
mRecapitalizeStatus.enable();
|
||||||
// We moved the cursor and need to invalidate the indicator right now.
|
// We moved the cursor and need to invalidate the indicator right now.
|
||||||
mTextDecorator.reset();
|
mTextDecorator.reset();
|
||||||
|
// Remaining background color that was used for the add-to-dictionary indicator should be
|
||||||
|
// removed.
|
||||||
|
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
||||||
// We moved the cursor. If we are touching a word, we need to resume suggestion.
|
// We moved the cursor. If we are touching a word, we need to resume suggestion.
|
||||||
mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
|
mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
|
||||||
true /* shouldDelay */);
|
true /* shouldDelay */);
|
||||||
|
@ -508,7 +497,9 @@ public final class InputLogic {
|
||||||
handler.cancelUpdateSuggestionStrip();
|
handler.cancelUpdateSuggestionStrip();
|
||||||
++mAutoCommitSequenceNumber;
|
++mAutoCommitSequenceNumber;
|
||||||
mConnection.beginBatchEdit();
|
mConnection.beginBatchEdit();
|
||||||
if (mWordComposer.isComposingWord()) {
|
if (!mWordComposer.isComposingWord()) {
|
||||||
|
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
||||||
|
} else {
|
||||||
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
|
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
|
||||||
// If we are in the middle of a recorrection, we need to commit the recorrection
|
// If we are in the middle of a recorrection, we need to commit the recorrection
|
||||||
// first so that we can insert the batch input at the current cursor position.
|
// first so that we can insert the batch input at the current cursor position.
|
||||||
|
@ -620,42 +611,6 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
mSuggestedWords = suggestedWords;
|
mSuggestedWords = suggestedWords;
|
||||||
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
|
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
|
||||||
if (shouldShowCommitIndicator(suggestedWords, settingsValues)) {
|
|
||||||
// typedWordInfo is never null here.
|
|
||||||
final int textBackgroundColor = settingsValues.mTextHighlightColorForCommitIndicator;
|
|
||||||
final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull();
|
|
||||||
handler.postShowCommitIndicatorTask(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// TODO: This needs to be refactored to ensure that mWordComposer is accessed
|
|
||||||
// only from the UI thread.
|
|
||||||
if (!mWordComposer.isComposingWord()) {
|
|
||||||
mTextDecorator.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final SuggestedWordInfo currentTypedWordInfo =
|
|
||||||
mSuggestedWords.getTypedWordInfoOrNull();
|
|
||||||
if (currentTypedWordInfo == null) {
|
|
||||||
mTextDecorator.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!currentTypedWordInfo.equals(typedWordInfo)) {
|
|
||||||
// Suggested word has been changed. This task is obsolete.
|
|
||||||
mTextDecorator.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: As with the above TODO comment, this operation must be performed only
|
|
||||||
// on the UI thread too. Needs to be refactored.
|
|
||||||
setComposingTextInternalWithBackgroundColor(typedWordInfo.mWord,
|
|
||||||
1 /* newCursorPosition */, textBackgroundColor);
|
|
||||||
mTextDecorator.showCommitIndicator(typedWordInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Note: It is OK to not cancel previous postShowCommitIndicatorTask() here. Having a
|
|
||||||
// cancellation mechanism could improve performance a bit though.
|
|
||||||
mTextDecorator.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put a blue underline to a word in TextView which will be auto-corrected.
|
// Put a blue underline to a word in TextView which will be auto-corrected.
|
||||||
if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
|
if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
|
||||||
|
@ -833,13 +788,14 @@ public final class InputLogic {
|
||||||
final InputTransaction inputTransaction,
|
final InputTransaction inputTransaction,
|
||||||
// TODO: remove this argument
|
// TODO: remove this argument
|
||||||
final LatinIME.UIHandler handler) {
|
final LatinIME.UIHandler handler) {
|
||||||
// In case the "add to dictionary" hint was still displayed.
|
if (!mWordComposer.isComposingWord()) {
|
||||||
// TODO: Do we really need to check if we have composing text here?
|
|
||||||
if (!mWordComposer.isComposingWord() &&
|
|
||||||
mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
|
|
||||||
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
|
|
||||||
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
||||||
mTextDecorator.reset();
|
// In case the "add to dictionary" hint was still displayed.
|
||||||
|
// TODO: Do we really need to check if we have composing text here?
|
||||||
|
if (mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
|
||||||
|
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
|
||||||
|
mTextDecorator.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int codePoint = event.mCodePoint;
|
final int codePoint = event.mCodePoint;
|
||||||
|
@ -1096,7 +1052,7 @@ public final class InputLogic {
|
||||||
inputTransaction.setRequiresUpdateSuggestions();
|
inputTransaction.setRequiresUpdateSuggestions();
|
||||||
} else {
|
} else {
|
||||||
if (mLastComposedWord.canRevertCommit()) {
|
if (mLastComposedWord.canRevertCommit()) {
|
||||||
revertCommit(inputTransaction);
|
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
|
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
|
||||||
|
@ -1582,14 +1538,19 @@ public final class InputLogic {
|
||||||
* This is triggered upon pressing backspace just after a commit with auto-correction.
|
* This is triggered upon pressing backspace just after a commit with auto-correction.
|
||||||
*
|
*
|
||||||
* @param inputTransaction The transaction in progress.
|
* @param inputTransaction The transaction in progress.
|
||||||
|
* @param settingsValues the current values of the settings.
|
||||||
*/
|
*/
|
||||||
private void revertCommit(final InputTransaction inputTransaction) {
|
private void revertCommit(final InputTransaction inputTransaction,
|
||||||
|
final SettingsValues settingsValues) {
|
||||||
final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
|
final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
|
||||||
|
final String originallyTypedWordString =
|
||||||
|
originallyTypedWord != null ? originallyTypedWord.toString() : "";
|
||||||
final CharSequence committedWord = mLastComposedWord.mCommittedWord;
|
final CharSequence committedWord = mLastComposedWord.mCommittedWord;
|
||||||
final String committedWordString = committedWord.toString();
|
final String committedWordString = committedWord.toString();
|
||||||
final int cancelLength = committedWord.length();
|
final int cancelLength = committedWord.length();
|
||||||
|
final String separatorString = mLastComposedWord.mSeparatorString;
|
||||||
// We want java chars, not codepoints for the following.
|
// We want java chars, not codepoints for the following.
|
||||||
final int separatorLength = mLastComposedWord.mSeparatorString.length();
|
final int separatorLength = separatorString.length();
|
||||||
// TODO: should we check our saved separator against the actual contents of the text view?
|
// TODO: should we check our saved separator against the actual contents of the text view?
|
||||||
final int deleteLength = cancelLength + separatorLength;
|
final int deleteLength = cancelLength + separatorLength;
|
||||||
if (DebugFlags.DEBUG_ENABLED) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
|
@ -1608,7 +1569,7 @@ public final class InputLogic {
|
||||||
if (!TextUtils.isEmpty(committedWord)) {
|
if (!TextUtils.isEmpty(committedWord)) {
|
||||||
mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
|
mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
|
||||||
}
|
}
|
||||||
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
|
final String stringToCommit = originallyTypedWord + separatorString;
|
||||||
final SpannableString textToCommit = new SpannableString(stringToCommit);
|
final SpannableString textToCommit = new SpannableString(stringToCommit);
|
||||||
if (committedWord instanceof SpannableString) {
|
if (committedWord instanceof SpannableString) {
|
||||||
final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
|
final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
|
||||||
|
@ -1645,23 +1606,53 @@ public final class InputLogic {
|
||||||
suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
|
suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
|
||||||
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
|
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean shouldShowAddToDictionaryForTypedWord =
|
||||||
|
shouldShowAddToDictionaryForTypedWord(mLastComposedWord, settingsValues);
|
||||||
|
|
||||||
if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
|
if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
|
||||||
// For languages with spaces, we revert to the typed string, but the cursor is still
|
// For languages with spaces, we revert to the typed string, but the cursor is still
|
||||||
// after the separator so we don't resume suggestions. If the user wants to correct
|
// after the separator so we don't resume suggestions. If the user wants to correct
|
||||||
// the word, they have to press backspace again.
|
// the word, they have to press backspace again.
|
||||||
mConnection.commitText(textToCommit, 1);
|
if (shouldShowAddToDictionaryForTypedWord) {
|
||||||
|
mConnection.commitTextWithBackgroundColor(textToCommit, 1,
|
||||||
|
settingsValues.mTextHighlightColorForAddToDictionaryIndicator,
|
||||||
|
originallyTypedWordString.length());
|
||||||
|
} else {
|
||||||
|
mConnection.commitText(textToCommit, 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For languages without spaces, we revert the typed string but the cursor is flush
|
// For languages without spaces, we revert the typed string but the cursor is flush
|
||||||
// with the typed word, so we need to resume suggestions right away.
|
// with the typed word, so we need to resume suggestions right away.
|
||||||
final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
|
final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
|
||||||
mWordComposer.setComposingWord(codePoints,
|
mWordComposer.setComposingWord(codePoints,
|
||||||
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
|
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
|
||||||
setComposingTextInternal(textToCommit, 1);
|
if (shouldShowAddToDictionaryForTypedWord) {
|
||||||
|
setComposingTextInternalWithBackgroundColor(textToCommit, 1,
|
||||||
|
settingsValues.mTextHighlightColorForAddToDictionaryIndicator,
|
||||||
|
originallyTypedWordString.length());
|
||||||
|
} else {
|
||||||
|
setComposingTextInternal(textToCommit, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Don't restart suggestion yet. We'll restart if the user deletes the separator.
|
// Don't restart suggestion yet. We'll restart if the user deletes the separator.
|
||||||
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
|
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
|
||||||
// We have a separator between the word and the cursor: we should show predictions.
|
|
||||||
inputTransaction.setRequiresUpdateSuggestions();
|
if (shouldShowAddToDictionaryForTypedWord) {
|
||||||
|
// Due to the API limitation as of L, we cannot reliably retrieve the reverted text
|
||||||
|
// when the separator causes line breaking. Until this API limitation is addressed in
|
||||||
|
// the framework, show the indicator only when the separator doesn't contain
|
||||||
|
// line-breaking characters.
|
||||||
|
if (!StringUtils.hasLineBreakCharacter(separatorString)) {
|
||||||
|
mTextDecorator.showAddToDictionaryIndicator(originallyTypedWordString,
|
||||||
|
mConnection.getExpectedSelectionStart(),
|
||||||
|
mConnection.getExpectedSelectionEnd());
|
||||||
|
}
|
||||||
|
mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString);
|
||||||
|
} else {
|
||||||
|
// We have a separator between the word and the cursor: we should show predictions.
|
||||||
|
inputTransaction.setRequiresUpdateSuggestions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2085,9 +2076,7 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits the chosen word to the text field and saves it for later retrieval. This is a
|
* Commits the chosen word to the text field and saves it for later retrieval.
|
||||||
* synonym of {@code commitChosenWordWithBackgroundColor(settingsValues, chosenWord,
|
|
||||||
* commitType, separatorString, Color.TRANSPARENT}.
|
|
||||||
*
|
*
|
||||||
* @param settingsValues the current values of the settings.
|
* @param settingsValues the current values of the settings.
|
||||||
* @param chosenWord the word we want to commit.
|
* @param chosenWord the word we want to commit.
|
||||||
|
@ -2096,23 +2085,6 @@ public final class InputLogic {
|
||||||
*/
|
*/
|
||||||
private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
|
private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
|
||||||
final int commitType, final String separatorString) {
|
final int commitType, final String separatorString) {
|
||||||
commitChosenWordWithBackgroundColor(settingsValues, chosenWord, commitType, separatorString,
|
|
||||||
Color.TRANSPARENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits the chosen word to the text field and saves it for later retrieval.
|
|
||||||
*
|
|
||||||
* @param settingsValues the current values of the settings.
|
|
||||||
* @param chosenWord the word we want to commit.
|
|
||||||
* @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_*
|
|
||||||
* @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none.
|
|
||||||
* @param backgroundColor the background color to be specified with the committed text. Pass
|
|
||||||
* {@link Color#TRANSPARENT} to not specify the background color.
|
|
||||||
*/
|
|
||||||
private void commitChosenWordWithBackgroundColor(final SettingsValues settingsValues,
|
|
||||||
final String chosenWord, final int commitType, final String separatorString,
|
|
||||||
final int backgroundColor) {
|
|
||||||
final SuggestedWords suggestedWords = mSuggestedWords;
|
final SuggestedWords suggestedWords = mSuggestedWords;
|
||||||
final CharSequence chosenWordWithSuggestions =
|
final CharSequence chosenWordWithSuggestions =
|
||||||
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
|
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
|
||||||
|
@ -2122,7 +2094,7 @@ public final class InputLogic {
|
||||||
// information from the 1st previous word.
|
// information from the 1st previous word.
|
||||||
final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
|
final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
|
||||||
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
|
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
|
||||||
mConnection.commitTextWithBackgroundColor(chosenWordWithSuggestions, 1, backgroundColor);
|
mConnection.commitText(chosenWordWithSuggestions, 1);
|
||||||
// Add the word to the user history dictionary
|
// Add the word to the user history dictionary
|
||||||
performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
|
performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
|
||||||
// TODO: figure out here if this is an auto-correct or if the best word is actually
|
// TODO: figure out here if this is an auto-correct or if the best word is actually
|
||||||
|
@ -2206,7 +2178,7 @@ public final class InputLogic {
|
||||||
private void setComposingTextInternal(final CharSequence newComposingText,
|
private void setComposingTextInternal(final CharSequence newComposingText,
|
||||||
final int newCursorPosition) {
|
final int newCursorPosition) {
|
||||||
setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition,
|
setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition,
|
||||||
Color.TRANSPARENT);
|
Color.TRANSPARENT, newComposingText.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2222,9 +2194,11 @@ public final class InputLogic {
|
||||||
* @param newCursorPosition the new cursor position
|
* @param newCursorPosition the new cursor position
|
||||||
* @param backgroundColor the background color to be set to the composing text. Set
|
* @param backgroundColor the background color to be set to the composing text. Set
|
||||||
* {@link Color#TRANSPARENT} to disable the background color.
|
* {@link Color#TRANSPARENT} to disable the background color.
|
||||||
|
* @param coloredTextLength the length of text, in Java chars, which should be rendered with
|
||||||
|
* the given background color.
|
||||||
*/
|
*/
|
||||||
private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText,
|
private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText,
|
||||||
final int newCursorPosition, final int backgroundColor) {
|
final int newCursorPosition, final int backgroundColor, final int coloredTextLength) {
|
||||||
final CharSequence composingTextToBeSet;
|
final CharSequence composingTextToBeSet;
|
||||||
if (backgroundColor == Color.TRANSPARENT) {
|
if (backgroundColor == Color.TRANSPARENT) {
|
||||||
composingTextToBeSet = newComposingText;
|
composingTextToBeSet = newComposingText;
|
||||||
|
@ -2232,7 +2206,8 @@ public final class InputLogic {
|
||||||
final SpannableString spannable = new SpannableString(newComposingText);
|
final SpannableString spannable = new SpannableString(newComposingText);
|
||||||
final BackgroundColorSpan backgroundColorSpan =
|
final BackgroundColorSpan backgroundColorSpan =
|
||||||
new BackgroundColorSpan(backgroundColor);
|
new BackgroundColorSpan(backgroundColor);
|
||||||
spannable.setSpan(backgroundColorSpan, 0, spannable.length(),
|
final int spanLength = Math.min(coloredTextLength, spannable.length());
|
||||||
|
spannable.setSpan(backgroundColorSpan, 0, spanLength,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
|
||||||
composingTextToBeSet = spannable;
|
composingTextToBeSet = spannable;
|
||||||
}
|
}
|
||||||
|
@ -2254,7 +2229,8 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo} is called.
|
* Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is
|
||||||
|
* called.
|
||||||
* @param info The wrapper object with which we can access cursor/anchor info.
|
* @param info The wrapper object with which we can access cursor/anchor info.
|
||||||
*/
|
*/
|
||||||
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
||||||
|
@ -2278,12 +2254,12 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the commit indicator should be shown or not.
|
* Returns whether the add to dictionary indicator should be shown or not.
|
||||||
* @param suggestedWords the suggested word that is being displayed.
|
* @param lastComposedWord the last composed word information.
|
||||||
* @param settingsValues the current settings value.
|
* @param settingsValues the current settings value.
|
||||||
* @return {@code true} if the commit indicator should be shown.
|
* @return {@code true} if the commit indicator should be shown.
|
||||||
*/
|
*/
|
||||||
private boolean shouldShowCommitIndicator(final SuggestedWords suggestedWords,
|
private boolean shouldShowAddToDictionaryForTypedWord(final LastComposedWord lastComposedWord,
|
||||||
final SettingsValues settingsValues) {
|
final SettingsValues settingsValues) {
|
||||||
if (!mConnection.isCursorAnchorInfoMonitorEnabled()) {
|
if (!mConnection.isCursorAnchorInfoMonitorEnabled()) {
|
||||||
// We cannot help in this case because we are heavily relying on this new API.
|
// We cannot help in this case because we are heavily relying on this new API.
|
||||||
|
@ -2292,24 +2268,16 @@ public final class InputLogic {
|
||||||
if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
|
if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull();
|
if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) {
|
||||||
if (typedWordInfo == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_TYPING){
|
if (TextUtils.equals(lastComposedWord.mTypedWord, lastComposedWord.mCommittedWord)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (settingsValues.mShowCommitIndicatorOnlyForAutoCorrection
|
if (!mDictionaryFacilitator.isUserDictionaryEnabled()) {
|
||||||
&& !suggestedWords.mWillAutoCorrect) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: Calling shouldShowAddToDictionaryHint(typedWordInfo) multiple times should be fine
|
return !mDictionaryFacilitator.isValidWord(lastComposedWord.mTypedWord,
|
||||||
// in terms of performance, but we can do better. One idea is to make SuggestedWords include
|
true /* ignoreCase */);
|
||||||
// a boolean that tells whether the word is a dictionary word or not.
|
|
||||||
if (settingsValues.mShowCommitIndicatorOnlyForOutOfVocabulary
|
|
||||||
&& !shouldShowAddToDictionaryHint(typedWordInfo)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,10 +97,7 @@ public final class SettingsValues {
|
||||||
new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
|
new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
|
||||||
|
|
||||||
// TextDecorator
|
// TextDecorator
|
||||||
public final int mTextHighlightColorForCommitIndicator;
|
|
||||||
public final int mTextHighlightColorForAddToDictionaryIndicator;
|
public final int mTextHighlightColorForAddToDictionaryIndicator;
|
||||||
public final boolean mShowCommitIndicatorOnlyForAutoCorrection;
|
|
||||||
public final boolean mShowCommitIndicatorOnlyForOutOfVocabulary;
|
|
||||||
|
|
||||||
// Debug settings
|
// Debug settings
|
||||||
public final boolean mIsInternal;
|
public final boolean mIsInternal;
|
||||||
|
@ -175,12 +172,6 @@ public final class SettingsValues {
|
||||||
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
|
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
|
||||||
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
|
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
|
||||||
prefs, mAdditionalFeaturesSettingValues);
|
prefs, mAdditionalFeaturesSettingValues);
|
||||||
mShowCommitIndicatorOnlyForAutoCorrection = res.getBoolean(
|
|
||||||
R.bool.text_decorator_only_for_auto_correction);
|
|
||||||
mShowCommitIndicatorOnlyForOutOfVocabulary = res.getBoolean(
|
|
||||||
R.bool.text_decorator_only_for_out_of_vocabulary);
|
|
||||||
mTextHighlightColorForCommitIndicator = res.getColor(
|
|
||||||
R.color.text_decorator_commit_indicator_text_highlight_color);
|
|
||||||
mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
|
mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
|
||||||
R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
|
R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
|
||||||
mIsInternal = Settings.isInternal(prefs);
|
mIsInternal = Settings.isInternal(prefs);
|
||||||
|
@ -426,12 +417,6 @@ public final class SettingsValues {
|
||||||
sb.append("" + (null == awu ? "null" : awu.toString()));
|
sb.append("" + (null == awu ? "null" : awu.toString()));
|
||||||
sb.append("\n mAdditionalFeaturesSettingValues = ");
|
sb.append("\n mAdditionalFeaturesSettingValues = ");
|
||||||
sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
|
sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
|
||||||
sb.append("\n mShowCommitIndicatorOnlyForAutoCorrection = ");
|
|
||||||
sb.append("" + mShowCommitIndicatorOnlyForAutoCorrection);
|
|
||||||
sb.append("\n mShowCommitIndicatorOnlyForOutOfVocabulary = ");
|
|
||||||
sb.append("" + mShowCommitIndicatorOnlyForOutOfVocabulary);
|
|
||||||
sb.append("\n mTextHighlightColorForCommitIndicator = ");
|
|
||||||
sb.append("" + mTextHighlightColorForCommitIndicator);
|
|
||||||
sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = ");
|
sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = ");
|
||||||
sb.append("" + mTextHighlightColorForAddToDictionaryIndicator);
|
sb.append("" + mTextHighlightColorForAddToDictionaryIndicator);
|
||||||
sb.append("\n mIsInternal = ");
|
sb.append("\n mIsInternal = ");
|
||||||
|
|
|
@ -37,6 +37,14 @@ public final class StringUtils {
|
||||||
|
|
||||||
private static final String EMPTY_STRING = "";
|
private static final String EMPTY_STRING = "";
|
||||||
|
|
||||||
|
private static final char CHAR_LINE_FEED = 0X000A;
|
||||||
|
private static final char CHAR_VERTICAL_TAB = 0X000B;
|
||||||
|
private static final char CHAR_FORM_FEED = 0X000C;
|
||||||
|
private static final char CHAR_CARRIAGE_RETURN = 0X000D;
|
||||||
|
private static final char CHAR_NEXT_LINE = 0X0085;
|
||||||
|
private static final char CHAR_LINE_SEPARATOR = 0X2028;
|
||||||
|
private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029;
|
||||||
|
|
||||||
private StringUtils() {
|
private StringUtils() {
|
||||||
// This utility class is not publicly instantiable.
|
// This utility class is not publicly instantiable.
|
||||||
}
|
}
|
||||||
|
@ -594,4 +602,30 @@ public final class StringUtils {
|
||||||
return sb + "]";
|
return sb + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the last composed word contains line-breaking character (e.g. CR or LF).
|
||||||
|
* @param text the text to be examined.
|
||||||
|
* @return {@code true} if the last composed word contains line-breaking separator.
|
||||||
|
*/
|
||||||
|
@UsedForTesting
|
||||||
|
public static boolean hasLineBreakCharacter(final String text) {
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = text.length() - 1; i >= 0; --i) {
|
||||||
|
final char c = text.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case CHAR_LINE_FEED:
|
||||||
|
case CHAR_VERTICAL_TAB:
|
||||||
|
case CHAR_FORM_FEED:
|
||||||
|
case CHAR_CARRIAGE_RETURN:
|
||||||
|
case CHAR_NEXT_LINE:
|
||||||
|
case CHAR_LINE_SEPARATOR:
|
||||||
|
case CHAR_PARAGRAPH_SEPARATOR:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue